Compare commits

...
Sign in to create a new pull request.

1025 commits

Author SHA1 Message Date
Robin Malfait
fa81d697fe
Improve style invalidation performance of group-* and peer-* variants (#20513)
# For humans, by @RobinMalfait

## TL;DR

Some people ran into performance issues with the current `group-*`
variant because of the `*` inside of the selector. Take the
`group-focus-visible:flex` class for example, this generates:
```css
.group-focus-visible\:flex:is(:where(.group):focus-visible *) {
  display: flex;
}
```

This slightly rewrites the selector by maintaining the same
functionality, but increasing performance because the browser has to do
fewer style recalculations.

That same class now produces:
```css
:is(:where(.group):focus-visible .group-focus-visible\\:flex) {
  display: flex;
}
```

# For AI, generated by AI

This PR changes the selectors generated for `group-*` and `peer-*`
variants so browsers do less style recalculation when a group or peer
changes state (e.g. on focus or hover). Which elements match, and with
what specificity, stays the same, except for one deliberately accepted
edge case involving `@namespace` (see below).

```css
/* Before */
.group-focus\:flex:is(:where(.group):focus *) { display: flex; }
.peer-focus\:flex:is(:where(.peer):focus ~ *) { display: flex; }

/* After */
:is(:where(.group):focus .group-focus\:flex) { display: flex; }
:is(:where(.peer):focus ~ .peer-focus\:flex) { display: flex; }
```

## Why

In the old form, the subject inside `:is(…)` is `*`. When `.group`
changes state, Chromium has to recalculate styles for **every**
descendant of the group (or every following sibling of the peer), not
just the elements that use the utility. With the target itself in that
position, the browser can narrow invalidation down to elements matching
the target.

## Performance

Synthetic benchmark: toggle focus on a group/peer, flush style after
each change, and measure only a non-layout property (`outline-color`) to
isolate invalidation. Apple M5 Pro, macOS arm64.

**Elements recalculated per focus/blur** (Chromium 151,
`UpdateLayoutTree` `elementCount`, including the focused element):

| Scenario                                    | Before |   After |
| ------------------------------------------- | -----: | ------: |
| Group: 100 targets among 10,000 descendants | 10,001 | **101** |
| Peer: 20 targets among 2,000 siblings       |  2,001 |  **21** |

**Median time per focus/blur** (sparse: 1% of elements carry the
utility):

| Engine       | Group: before → after     | Peer: before → after      |
| ------------ | ------------------------- | ------------------------- |
| Chromium 151 | 2.169 → **0.130 ms** (~17×) | 0.635 → **0.192 ms**
(~3.3×) |
| Firefox 153  | 0.500 → 0.350 ms          | 0.650 → 0.600 ms          |
| WebKit 26.5  | 0.750 → 0.750 ms          | 0.450 → 0.450 ms          |

**Dense case** (every element carries the utility): no meaningful
difference in any engine, because every element needs recalculation
anyway. Firefox's dense group case was ~5% slower (2.775 → 2.925 ms).
Everything else was within noise.

| Engine | Group dense: before → after | Peer dense: before → after |
| ------------ | --------------------------- |
-------------------------- |
| Chromium 151 | 4.027 → 3.971 ms | 18.095 → 18.121 ms |
| Firefox 153 | 2.775 → 2.925 ms | 43.900 → 44.025 ms |
| WebKit 26.5 | 7.550 → 7.425 ms | 35.325 → 35.100 ms |

These are micro-benchmarks of style updates, not page-load or frame-rate
numbers. The real-world gain depends on DOM size and how many elements
inside a group/peer use the variant. The biggest win is the common case:
a large group containing only a handful of `group-*` targets.

## Do the selectors behave the same?

Yes. For a group condition `G` and a target `&`:

- **Before:** matches `&` and has an ancestor matching `G`
- **After:** has an ancestor matching `G` and matches `&`

`peer-*` follows the same reasoning with `~`. Details:

- **Specificity is unchanged.** `:is()` takes the specificity of its
argument, so before was `spec(&) + spec(G)` and after is `spec(G) +
spec(&)`.
- **`&` is used, not the utility class**, so `@apply`, `@variant`,
`*:group-*`, `[&_p]:group-*`, and other variants that change the target
keep working. Complex parents such as `.foo .bar { @apply
peer-focus:flex }` keep `:is(…)` semantics during nesting: `:is(P ~
:is(.foo .bar))`.
- **`&` appears only once**, so stacked variants grow the selector
linearly. A unit test with 12 stacked variants guards against
exponential growth.
- **The selectors are also shorter:** 6 bytes of wrapping instead of 7.
- **The outer `:is(…)`** keeps compound variants such as `has-group-*`,
`not-group-*`, and `in-group-*` equivalent. For example, `has-group-*`
can still match when the group sits outside the element carrying the
utility.

**Accepted edge case:** if a stylesheet declares a default `@namespace`,
the old trailing `*` limited matches to elements in that namespace.
Inside compound variants such as `group-group-*` or `has-group-*`, the
new selector no longer does, so an SVG element (e.g. inside
`foreignObject`) can now count as the inner group. Appending `:is(*)` to
the target would restore the old behavior with no performance cost, but
it makes every selector longer for a combination (`@namespace` + mixed
namespaces + compound group variants) that is very unlikely in practice.
We can add it back if anyone runs into this.

There's one known browser quirk this PR doesn't change: Chromium doesn't
invalidate `has-group-*` when the focused group is an ancestor *outside*
the element. That happens with both the old and new selectors.

## Test plan

- [x] Updated unit test snapshots for the new selector shape
- [x] New unit test that bounds selector size with 12 stacked variants
- [x] New browser tests in `packages/tailwindcss/tests/ui.spec.ts`, run
in Chromium, Firefox, and WebKit. They cover `group-*`/`peer-*` focus
and blur, `@apply` inside a complex selector, specificity, stacked
groups in either order, and compound `group-peer-*`/`peer-group-*`. The
pre-PR selectors pass all of them too, so behavior is unchanged
- [x] `pnpm run test` and `pnpm run test:ui` pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 21:32:41 +02:00
Dex
9798a8ab5b
fix: correct two comment typos (#20507)
Fixes two typos in code comments (no behavior affected):

- `packages/@tailwindcss-cli/src/commands/build/index.ts`: `Succesfully`
-> `Successfully`
- `packages/tailwindcss/src/candidate.ts`: `preceeded` -> `preceded`

Co-authored-by: Dextheking1 <4175097+Dextheking1@users.noreply.github.com>
2026-09-25 11:28:27 +00:00
한국
722acdfebf
Don't treat \ as an escape inside CSS comments (#20508)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

The CSS parser treated `\` inside comments as an escape and skipped the
character after it. Because of that, a comment ending in `\*/` was never
closed, and the CSS that followed was swallowed or turned into a broken
rule:

```css
/* C:\temp\*/
.a { color: red }
```

Before this change the `.a` rule disappeared entirely, and `/* \*/ .a {
color: red }` produced the selector `* \*/ .a`. The same thing happened
to comments inside declaration values, where the comment ran on until
the next `*/` it could find, pulling following declarations into the
value.

Per [CSS Syntax Level 3
§4.3.2](https://www.w3.org/TR/css-syntax-3/#consume-comment), a comment
ends at the first `*/` and escapes aren't processed inside comments.
This removes the backslash handling from both comment-scanning loops in
`css-parser.ts` (top-level and inside declaration values).

The existing test `/*Hello, \*\/ world!*/` keeps passing, since that
input contains no `*/` before the final one.

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

Added two tests to `packages/tailwindcss/src/css-parser.test.ts` (both
run with Unix and Windows line endings):

- a top-level comment ending in `\*/` followed by a rule
- a comment ending in `\*/` inside a custom property value, followed by
another declaration

Both fail without the change to `css-parser.ts` and pass with it.

```sh
pnpm vitest run packages/tailwindcss/src/css-parser.test.ts
pnpm vitest run --project tailwindcss
pnpm run lint
```

---------

Co-authored-by: Robin Malfait <robin.malfait@shopify.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-09-25 11:16:58 +00:00
Alexandre Kohler
86f3daf912
Sort breakpoint variants with decimal values numerically (#20512)
## Summary

`compareBreakpoints` compared values in the same unit with `parseInt`,
which drops the fractional part, so `40.25rem` and `40.5rem` were
treated as equal. As a result `min-[40.5rem]:*` could be emitted before
`min-[40.25rem]:*` (the same happened for `@min-*` container queries),
which means the smaller breakpoint wins in the cascade. This switches
the comparison to `parseFloat` so decimal values are ordered
numerically; values that were already sorted correctly are unaffected.

## Test plan

- Added a test to `packages/tailwindcss/src/variants.test.ts` that sorts
`min-[40.25rem]`, `min-[40.5rem]`, `max-[40.25rem]` and `max-[40.5rem]`.
It fails without the change (the `min-[40.5rem]` rule is emitted before
`min-[40.25rem]`) and passes with it.
- `vitest run src/variants.test.ts -t "decimal values"` (in
`packages/tailwindcss`)
- `vitest run` (in `packages/tailwindcss`): 42 files, 5009 tests passed
- `prettier --check` on the changed files
2026-09-25 11:07:37 +00:00
cui fliter
41d9cae8e5
Reject candidates with multiple modifiers (#20466)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

`segment()` preserves empty top-level segments, but the candidate and
variant parsers currently use the truthiness of the third segment to
detect additional modifiers.

As a result, inputs such as `bg-red-500/50/`, `bg-red-500/50//foo`,
`group-hover/foo/:flex`, and `group-hover/foo//bar:flex` can be parsed
as valid candidates even though they contain multiple slash modifier
segments.

This change checks the number of segments instead of the value of the
third segment. Single modifiers such as `bg-red-500/50` and
`group-hover/foo:flex` continue to parse normally, while all additional
top-level `/` segments are rejected.


## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

- `pnpm exec vitest run packages/tailwindcss/src/candidate.test.ts
--hideSkippedTests`
- `pnpm exec vitest run packages/tailwindcss/src --hideSkippedTests`
- `pnpm exec prettier --check packages/tailwindcss/src/candidate.ts
packages/tailwindcss/src/candidate.test.ts`

`pnpm --filter=tailwindcss lint` was also attempted, but currently fails
on existing cross-package dependency and Bun type errors unrelated to
this change.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-09-08 13:46:59 +00:00
Wei
f723e834ad
Don't warn about Angular's ::ng-deep and :host-context() when optimizing CSS (#20434)
Fixes #20433.

The warning filter in `optimize.ts` already ignores `:deep()`,
`:slotted()` and `:global()`. Angular's two deep selectors are the same
kind of thing — non-standard pseudo-selectors that the framework's
compiler resolves before the CSS reaches a browser — but they aren't
covered, so every Angular component stylesheet using them prints a
warning block per occurrence.

```
Found 2 warnings while optimizing generated CSS:

Issue #1:
│ :host ::ng-deep .some-child, :host
┆        ^-- 'ng-deep' is not recognized as a valid pseudo-element. Did you mean ':ng-deep' (pseudo-class) or is this a typo?
```

Angular strips both during view-encapsulation shimming — `::ng-deep` via
`_shadowDeepSelectors = /(?:>>>)|(?:\/deep\/)|(?:::ng-deep)/g`, and
`:host-context()` in the same pass — so neither ever reaches a browser.

Worth noting that `/deep/` and `>>>`, Angular's two other spellings of
the deep selector, already pass silently because
`nonStandard.deepSelectorCombinator` is enabled. `::ng-deep` is the only
one that warns, and it's the spelling the Angular docs use — so in
practice every Angular codebase hits this. On the workspace where I ran
into it (7 Angular apps), a production build printed 425 `ng-deep`
warnings and 2 `host-context` ones.

## Test plan

There's no automated coverage here because the warning path is behind
`process.env.NODE_ENV !== 'test'`, so a spy sees nothing under Vitest
regardless of the filter — the same reason #20277 shipped without one.
Instead I ran the file before and after the change against Lightning CSS
1.33.0 directly, counting emitted warning blocks:

| Input | Before | After |
| --- | --- | --- |
| `:host ::ng-deep .a, :host ::ng-deep .b { … }` | 1 | 0 |
| `:host-context(.dark) .a { … }` | 1 | 0 |
| `:deep(.a) { … }` | 0 | 0 |
| `.a::totally-not-real { … }` — genuine typo | 1 | **1** |

Generated CSS is byte-identical before and after; only the warning is
suppressed. Genuine unknown pseudo-selectors still warn, so the typo
hint the message exists for is preserved.

Happy to restructure this if you'd prefer the predicate extracted so it
can be unit-tested, or to split `ng-deep` and `host-context` into their
own block rather than extending the existing regex.

A minimal reproduction of the original issue is at
https://gist.github.com/weilinzung/ace42ceb95c2f47b6747cc1f34f162bd.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-08-31 10:14:54 +00:00
Simon Bobrov
9f451eead8
Cold start performance improvements (#20427)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

Hi guys, thanks for you great work!

I work on performance improvements on
https://github.com/schoero/eslint-plugin-better-tailwindcss project.
Part of issues can be fixed on the tailwind side only.

This is a first fix, I have a bigger one in mind, it would require a
small additional public API method - out of scope of this PR.

Please let me know what you think.

## Finding

`getVariantOrder()` re-sorts all parsed variants with the (expensive)
variant comparator on every call, and it is called by every
compileCandidates() invocation — per build pass, per @apply
substitution, and hundreds of times during candidate canonicalization
via the variant signature caches. Since parsedVariants is append-only,
the computed order only changes when a new variant is parsed, so we can
cache the result and invalidate on parsedVariants.size. This makes cold
canonicalization (IntelliSense, lint plugins) faster and removes
repeated sorting from @apply substitution and incremental rebuilds.

## Test plan

Tested at https://github.com/smnbbrv/better-tailwindcss-bench . The
relevant part is the `epbt-now / tw-patched`

**plugin cost** (ms, cold run net of parse baseline)

| codebase | epbt-now / tw-now | epbt-patched / tw-now | epbt-now /
tw-patched | epbt-patched / tw-patched |
| ---------- | ----------------: | --------------------: |
--------------------: | ------------------------: |
| mixed | 6292 | 3965 (-37%) | 5395 (-14%) | 3595 (-43%) |
| repetitive | 5279 | 2425 (-54%) | 4819 (-9%) | 2124 (-60%) |
| unique | 6096 | 5458 (-10%) | 5507 (-10%) | 5295 (-13%) |
2026-08-31 10:05:03 +00:00
Srinu desetti
90f8ff41c8
Don't space out and/or/not inside function calls in supports-[…] variants (#20420)
## Summary

The `supports-[…]` variant works around a Chrome bug where `@supports
(a)or(b)`
is invalid by spacing out the `and`, `or`, and `not` keywords. However,
the
replacement is applied to the entire value, including the inside of
function
calls, where these words can be part of a selector.

For example, `supports-[selector(a:not(.foo))]:flex` generates:

```css
@supports selector(a: not (.foo))
```

The selector `a: not (.foo)` is unparsable, so a condition that is true
in
every browser silently becomes false and the utility never applies. The
same
happens to class names like `.and` or `.or` inside `selector(…)`.

This PR only spaces out the keywords at the condition level: parens
preceded by
an identifier (other than the keywords themselves) start a function
call, and
everything inside is left as-is. The Chrome workaround still applies to
the
condition itself, e.g. `supports-[(display:grid)or(display:flex)]` still
becomes `@supports (display: grid) or (display: flex)`.

## Test plan

- Added a test covering `selector(a:not(.foo))`, class names
`.and`/`.or`
inside `selector(…)`, the Chrome `(a)or(b)` workaround, and a top-level
  `not(…)` condition.
- `pnpm vitest run packages/tailwindcss/src/variants.test.ts` — 102
passed.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-08-14 19:43:33 +00:00
Srinu desetti
7a7f386648
Don't emit utilities that resolve a theme value when an unsupported modifier is used (#20419)
## Summary

Modifiers on functional utilities that resolve a named theme value are
silently
dropped instead of invalidating the candidate. For example, with the
default theme:

- `rounded-sm/[5]` emits the same CSS as `rounded-sm` (the `[5]` is
ignored)
- `shadow-sm/foo`, `inset-shadow-sm/foo`, `text-shadow/foo`, and
`drop-shadow/foo`
  emit the full shadow CSS with the invalid `foo` modifier ignored

This is inconsistent with how every sibling code path behaves:
`rounded/foo`,
`rounded-[4px]/foo`, `rounded-sm/5`, and `drop-shadow-xl/foo` all
correctly
produce no output, because those paths check `candidate.modifier`.

This PR adds the missing guards:

- In the generic `functionalUtility` handler, a candidate whose named
value
resolves from the theme now rejects modifiers (except fractions like
`w-1/2`,
  where the modifier is part of the resolved value).
- The `shadow`, `inset-shadow`, and `text-shadow` utilities now apply
the same
`if (candidate.modifier && !alpha) return` guard in their default-value,
arbitrary-value, and named-size branches that `drop-shadow` already
applies,
  and `drop-shadow` gets it in its default-value branch too.

Existing tests asserting candidates like `drop-shadow/foo` produce no
output were
passing for the wrong reason: they run without a theme, so the theme
lookup fails
before the modifier is ever considered. The new tests provide a theme so
the
invalid modifier is what invalidates the candidate.

## Test plan

- Added assertions to the `rounded`, `filter`, `shadow`, `inset-shadow`,
and
`text-shadow` tests that compile candidates with invalid modifiers
against a
theme that defines the relevant values, and expect no output. All of
them fail
  without the fix.
- `pnpm vitest run packages/tailwindcss/src/utilities.test.ts` — 398
passed.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-08-14 16:47:34 +00:00
Robin Malfait
021b7fe6f2
Canonicalization: prevent inlining CSS-wide keywords (#20417)
This PR fixes an issue where canonicalization suggestions in
intellisense result in 'weird' suggestions.

```
The class text-foreground/60 can be written as text-default-soft-hover
```

If we look at the CSS provided by the issue, this doesn't immediately
make sense:
```css
@theme {
  --color-foreground: var(--foreground);
  --color-default-soft-hover: color-mix(in oklab, var(--default) 60%, transparent);
}

:root {
  --foreground: oklch(0.2103 0.0059 285.89); /* near-black */
  --default: oklch(94% 0.001 286.375); /* light gray */
}
```

But it turns out that when you use Uniwind (React Native) with HeroUI,
that the setup looks more like this:
```css
@import 'tailwindcss';

@theme {
  --foreground: unset;
  --default: unset;
}

@theme inline {
  --color-foreground: var(--foreground);
  --color-default-soft-hover: color-mix(in oklab, var(--default) 60%, transparent);
}
```

During the canonicalization step, we inline all the `@theme` values, the
reason for this is that `text-[#fff]` can be turned into `text-white`
even though they look slightly different:
```css
.text-\[\#fff\] {
  color: #fff;
}
.text-white {
  color: var(--color-white, #fff);
}
```

But when we inline them, it looks like this:
```css
.text-\[\#fff\] {
  color: #fff;
}
.text-white {
  color: #fff;
}
```

Internally, we use signatures to make sure that they are safe to be
subtituted with eachother. In this case, the signatures will look like
this:
```css
.x {
  color: #fff;
}
.x {
  color: #fff;
}
```

If we now look at the signatures of the original issue, you would see:
```css
/* text-foreground/60 */
.x {
  color: color-mix(in_oklab,unset_60%,transparent);
}

/* text-default-soft-hover */
.x {
  color: color-mix(in_oklab,unset_60%,transparent);
}
```

That's because the `@theme` variables were inlined, resulting in the
exact same signature, thus we consider them the same.

This PR makes sure to never inline [CSS-wide
keywords](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/Data_types#css-wide_keywords)
(such as `unset`) and therefore keeping the CSS variable reference:
```css
/* text-foreground/60 */
.x {
  color: color-mix(in_oklab,var(--foreground)_60%,transparent);
}

/* text-default-soft-hover */
.x {
  color: color-mix(in_oklab,var(--default)_60%,transparent);
}
```

Fixes:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1610

## Test plan

1. Existing tests pass
2. Added a regression test

Checked manually in the regression repo

Before:
<img width="1358" height="421" alt="image"
src="https://github.com/user-attachments/assets/8ab3de97-17e4-4e11-b335-bb37218ff2ee"
/>

After:
<img width="1177" height="247" alt="image"
src="https://github.com/user-attachments/assets/cbbfcf04-3ed3-4fa7-a3eb-ef607239f4ad"
/>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-08-14 12:16:00 +02:00
Robin Malfait
f7f58f0806
Move debug logs in .tailwindcss folder (#20416)
Instead of writing debug logs (triggered by using `DEBUG=*`) in the root
of the project as `tailwindcss-<pid>.log`, we will write them to
`.tailwindcss/logs/scanner-<timestamp>-<pid>.log` instead.

The reasoning for this is that we won't pollute the root of the project.
Another reason is that if you don't ignore `.log` files via
`.gitignore`, it could be that you end up in a loop if you use `DEBUG=*
vite` because a new `.log` file might trigger a new build. We noticed
this while looking at
https://github.com/tailwindlabs/tailwindcss/discussions/20382.

The `.tailwindcss` folder will come with a `.gitignore` file that
ignores everything (`*`).

## Test plan

1. Nothing really changed here, all tests should pass


Testing this in a local project, it looks like this:
<img width="483" height="165"
alt="file-35bfc43606c556f2380a161c7595d103"
src="https://github.com/user-attachments/assets/629519c8-8f2b-46ee-8046-9ba3749f319a"
/>
2026-08-13 23:16:30 +02:00
Robin Malfait
de9e71c599
fix referenced link 2026-08-13 21:31:51 +02:00
Robin Malfait
00ef99df3d
Do not force full page reloads when using @tailwindcss/vite (#20414)
This PR removes all of the custom HMR handling we had in the
`@tailwindcss/vite` plugin.

When Vite 7.1 was introduced, Vite stopped performing a full page reload
for unknown files and instead started performing normal `hmr` updates.
This resulted in this issue:
https://github.com/tailwindlabs/tailwindcss/issues/19637

At the time, it felt like something we could easily re-add: if a file is
not covered by Vite, we can perform a `full-reload`. This meant that a
`.php` file would trigger a full page reload as expected.

The reason the `.php` file triggered Vite in the first place is because
those files were scanned by us (`@tailwindcss/vite`) so it made sense.

However, this then resulted in a plethora of issues, and it feels a bit
like a game of whac-a-mole.

- https://github.com/tailwindlabs/tailwindcss/issues/19744
- https://github.com/tailwindlabs/tailwindcss/issues/19903
- https://github.com/tailwindlabs/tailwindcss/issues/20320
- https://github.com/tailwindlabs/tailwindcss/issues/20378
- https://github.com/tailwindlabs/tailwindcss/issues/20411

Fixes: #19744
Fixes: #19903
Fixes: #20320
Fixes: #20378
Fixes: #20411

We kept updating the logic by safelisting certain extensions, checking
different servers and/or environments, handling the fact that `server`
in the callback could be absent in `experimental.bundledDev` mode, etc.
etc.

Now, when investigating the last issue
(https://github.com/tailwindlabs/tailwindcss/issues/20411), I can
trigger full reloads by changing `.json`, `.yaml` or `.svg` files. This
makes sense since they aren't handled by default.

So thinking about this more, I think it's just not Tailwind's
responsibility to tell Vite to reload the browser or not. Yes, we use
the `addWatchFile` API, so files are being watched because of us.
However, our only goal is to update the `.css` file (and HMR that).

This means that we can just drop all the custom HMR handling we have in
`@tailwindcss/vite`.

This also means that
https://github.com/tailwindlabs/tailwindcss/issues/19637 would regress
and won't trigger full page reloads. But this can be easily handled by a
plugin responsible for this behavior:

- https://github.com/ElMassimo/vite-plugin-full-reload

## Test plan

1. All tests pass
2. Manually tested and changing unknown files don't result in a full
page reload
2026-08-13 17:00:08 +02:00
Matt Van Horn
b9286a7346
fix: Drop invalid UTF-8 scanner candidates (#20389)
## Summary

Replace the three unchecked scanner conversions in
`crates/oxide/src/scanner/mod.rs` with checked conversions that omit
only extracted slices that are not valid UTF-8. Apply the check in the
shared `extract` pipeline so initial scans, incremental `scan_content`
calls, and CSS-variable extraction cannot insert invalid strings into
scanner state, and apply the same policy to both branches of
`get_candidates_with_positions` while preserving byte offsets and the
legacy `-[]` restoration. Keep the extractor's byte-oriented CSS
identifier classification unchanged: accepting non-ASCII bytes during
extraction is useful for valid multibyte code points, while the
conversion boundary is the authoritative place to enforce the `String`
contract.

The scanner currently converts extracted byte slices with unchecked
UTF-8 constructors at the shared extraction boundary and both
candidate-with-position branches. A source file containing a stray
continuation byte can therefore produce an invalid `String`, violating
Rust's string invariant and allowing corrupted candidates to persist in
a long-lived scanner. The thread provides a deterministic reproduction
using invalid bytes inside an arbitrary value, so the problem no longer
depends on reproducing the originally reported Turbopack race. Valid
candidates found alongside malformed byte sequences must continue to be
returned normally.

Fixes #20368

## Test plan

Not applicable to this change.

AI was used for assistance.

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-08-13 13:01:13 +02:00
Robin Malfait
84bea42b6c
Visualize scanner related tests (#20412)
This PR improves the scanner related tests a bit. Right now all the
scanner tests (a lot of them) are somewhat hard to reason about and to
figure out whether these are actually correct or not.

With this PR, the existing are extended with a visualization of the file
system tree, but with indicators to know whether or not a file was
scanned or not. If a `.gitignore` is present, it's content is also shown
to make it easier to see why some files are included or not included.

One of the outputs look like this:
```
.
├── ✗ .gitignore
│       src/ignored-by-gitignore.html
└── ✓ src
    ├── ✗ ignore-by-extension.bin
    ├── ✓ ignored-by-gitignore.html
    ├── ✗ ignored-by-source-not.html
    ├── ✓ keep-me.html
    └── ✓ new-file.html
```

I'm sure we can cleanup a lot of these tests, simplify, deduplicate, but
I didn't want to do that as part of this PR. This PR is purely adding
this visualization.

This uses the [insta.rs](https://insta.rs) library for (inline) snapshot
tests. If at some point the snapshots change, then you get a nice UI:
<img width="958" height="670" alt="image"
src="https://github.com/user-attachments/assets/652cf48f-e1b1-4fe5-a42a-29c5a888063a"
/>


## Test plan

1. All tests should pass [ci-all]
2. Nothing in the code itself changed, so nothing should break

---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-08-13 12:45:40 +02:00
Robin Malfait
606438c6c3
rebase ignore crate from 0.4.24 to 0.4.33 (#20409)
This PR updates the vendored `ignore/` crate to make use of the upstream
improvements.

We can't fully use the native `ignore` crate yet because of the missing
functionality that we added ourselves. Our own changes are still marked
as `CHANGED:`

## Test plan

1. All tests should still pass [ci-all]

---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-08-13 12:45:40 +02:00
Syed Sohail Hussain
8ac18c7e72
test: fix 'with with' typo in css parser tests (#20410)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->
2026-08-13 11:05:30 +02:00
Marick van Tuil
b86a6e0a0d
Fix slow Vite rebuilds in projects with large gitignored directories (#20408)
Preface: For full transparency, I used AI (Claude) to help dig into this
issue we're having and write some of the explanation below.

Our local Laravel application (with Docker) takes between 2-6s seconds
(re)building `app.css` after each Blade file update. After checking with
Claude, there seems to be some potential unnecessary traversing of big
(gitignored) directories, specifically `vendor` and `storage`.

As far as I understand it, two scans happen in tailwindcss:

- a first scan, the content scan. This one reads the project files
(auto-detect). It respects .gitignore, so it correctly skips vendor/ and
storage/.

- a second scan, the glob resolution (resolve_globs() in the oxide
crate). It builds watch patterns that the Vite plugin registers, so a
file save can trigger a CSS rebuild. It walks the project again, but
this walk does not respect .gitignore. It only skips a hardcoded list of
directory names from `ignored-content-dirs.txt` (node_modules, .git,
venv, ...). vendor/ and storage/ are not on that list, so it descends
into them and stats every file. These directories can never produce a
watch pattern, because the content scan never visited them. That work is
perhaps wasted?

Maybe why this went unnoticed: on a native filesystem a stat takes about
1µs, so 57k of them is around 0.06s. A Docker bind mount multiplies that
by roughly 100. And the big directory in a typical JS project is
`node_modules`, which the name list already catches; `vendor/` is the
PHP ecosystem's `node_modules`, and it is not on that list. (#19616 /
#19632 fixed the same problem for `scanner.files`; the CLI never reads
`scanner.globs`, so it was never affected.)

The fix: the second walk now skips any directory the scan never visited,
the same pruning the first walk (line 73) already does.

Measured on our local project: `get_globs()` after `scan()` went from
9.9s to somewhere between 0.2 and 1.0s across runs, with identical
output (35 globs). A template save now reloads in about 0.3s instead of
4.

## Test plan

- `cargo test` in `crates/oxide`: 66 tests pass, including the glob
assertions for nested and gitignored directories in `tests/scanner.rs`.
- Compared the sorted `get_globs()` output before and after on the
project above: identical.

I've also set up a repo that reproduces the issue in a fresh Laravel
app:

https://github.com/marickvantuil/tailwind-scan-reproduce

The results:

| Environment | vendor/ | scan | get_globs | syscalls under vendor/ |
| --- | --- | --- | --- | --- |
| GitHub Actions (native FS) | present | 5.0 ms | 48.8 ms | 11,410 |
| GitHub Actions (native FS) | absent | 4.5 ms | 0.9 ms | n/a |
| macOS, Docker bind mount | present | 15.8 ms | 599.4 ms | 11,410 |
| macOS, Docker bind mount | absent | 16.5 ms | 6.5 ms | n/a |

---------

Co-authored-by: Marick van Tuil <marick@dolphiq.nl>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-08-12 14:31:47 +00:00
Ridwan Olanrewaju
16e94cbf7f
Make createCompatConfig theme mappings safe to spread when a namespace returns a string (#20399)
When building the legacy config compat layer, namespace mappings like
`fontSize` → `text`, `boxShadow` → `shadow`, `animation` → `animate`,
etc. spread the result of `theme(namespace, {})` directly:

```ts
...(theme('text', {}) ?? {})
```

Some of these namespaces can return a **string** (e.g. `theme('text',
{})` → `'1rem'`). Spreading a string produces char-indexed keys (`{ '0':
'1', '1': 'r', ... }`) instead of a `{ DEFAULT: ... }` entry, silently
corrupting the compat config output.

This PR adds a small `spreadTheme` helper that normalizes the return
value before spreading:

- `string` → `{ DEFAULT: value }`
- plain object → `{ ...value }`
- anything else → `{}`

and updates all namespace mappings to use it. Includes unit tests for
the helper plus integration tests for the affected namespaces and the
string-return regression cases.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-08-10 15:22:33 +00:00
Robin Malfait
46df7ee2fc
Don't scan ignored folders using .gitignore safelist setup (#20397)
This PR fixes an issue where a folder containing a `.gitignore` that
uses a whitelist setup results in too many folders being scanned:

```gitignore
# .gitignore
/*
!/app
```

We want to make sure that we respect the `.gitignore` file, but we also
need to make sure that `@source` directives 'win' from `.gitignore`
files. For example:

```css
@import 'tailwindcss';
@source "./node_modules/my-package/*.js";
```

In this case, `node_modules` is likely ignored via `.gitignore`, but you
_do_ want to include the files from the `my-package` folder, essentially
overriding the `.gitignore` rules.

To do this, we internally mark this folder as an `External` folder such
that it gets read, because that's what we want.

The current bug has a pattern like `/*` and then safelist/whitelist
patterns to include certain folders. The problem with the `/*` pattern
is that this convert the _current_ folder to an `External` folder. This
is wrong. The current folder cannot be ignored by the current
`.gitignore` file itself. You can ignore files and folders _inside_ the
current folder, but not the folder itself. The current folder can only
be ignored by parent `.gitignore` rules.

Originally discussed in:
https://github.com/tailwindlabs/tailwindcss/discussions/20382
Fixes: #20385

## Test plan

1. Added regression tests
2. Added integration tests

Manually tested, and logging the scanned files does result in some
interesting results:


<details>

<summary>Before</summary>

```
Reading ".git/COMMIT_EDITMSG"
Reading ".git/HEAD"
Reading ".git/MERGE_RR"
Reading ".git/config"
Reading ".git/description"
Reading ".git/hooks/applypatch-msg.sample"
Reading ".git/hooks/commit-msg.sample"
Reading ".git/hooks/fsmonitor-watchman.sample"
Reading ".git/hooks/post-update.sample"
Reading ".git/hooks/pre-applypatch.sample"
Reading ".git/hooks/pre-commit.sample"
Reading ".git/hooks/pre-merge-commit.sample"
Reading ".git/hooks/pre-push.sample"
Reading ".git/hooks/pre-rebase.sample"
Reading ".git/hooks/pre-receive.sample"
Reading ".git/hooks/prepare-commit-msg.sample"
Reading ".git/hooks/push-to-checkout.sample"
Reading ".git/hooks/sendemail-validate.sample"
Reading ".git/hooks/update.sample"
Reading ".git/index"
Reading ".git/info/exclude"
Reading ".git/logs/HEAD"
Reading ".git/logs/refs/heads/main"
Reading ".git/objects/02/2fe3aa1b28300c0146acdff4eb3240cd2a861b"
Reading ".git/objects/17/45509b3ec34de9d8ddd6708c9d73f191c63c48"
Reading ".git/objects/1e/d83f044a80cfec0843a0d68fa859c101e4e02f"
Reading ".git/objects/60/76888f3ae7ecd2712c236933314e1c420e52f2"
Reading ".git/objects/76/e5d55dc7d51e47bff78b27e39a13110e17c072"
Reading ".git/objects/89/7b4529b9a3db489cc00e2cff4bbd1a2f09fb50"
Reading ".git/objects/8c/781ce3374b53455c284bef51bffbee5ca88969"
Reading ".git/objects/b2/ceaa157eef0b2a8d0231a1396b17684da71de7"
Reading ".git/objects/da/7ef6439d0779bae07668b42d77dcbce2f84111"
Reading ".git/refs/heads/main"
Reading ".gitattributes"
Reading ".gitignore"
Reading "app/index.html"
Reading "build/index.html"
Reading "node_modules/.modules.yaml"
Reading "node_modules/.package-map.json"
Reading "node_modules/.pnpm-workspace-state-v1.json"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/LICENSE"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/README.md"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/package.json"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/src/scopes.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/src/sourcemap-codec.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/src/strings.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/src/vlq.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/strings.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/strings.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/strings.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/strings.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/LICENSE"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/README.md"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.mjs"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.mjs.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.umd.js"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.umd.js.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/package.json"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/src/binary-search.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/src/by-source.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/src/flatten-map.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/src/resolve.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/src/sort.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/src/sourcemap-segment.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/src/strip-filename.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/src/trace-mapping.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/src/types.ts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/binary-search.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/binary-search.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/binary-search.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/binary-search.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/by-source.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/by-source.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/by-source.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/by-source.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/resolve.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/resolve.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/resolve.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/resolve.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/sort.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/sort.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/sort.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/sort.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/types.d.cts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/types.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/types.d.mts"
Reading "node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/trace-mapping/types/types.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/LICENSE"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/README.md"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.mjs"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.mjs.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.umd.js"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.umd.js.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/dist/types/gen-mapping.d.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/dist/types/set-array.d.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/dist/types/sourcemap-segment.d.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/dist/types/types.d.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/package.json"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/src/gen-mapping.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/src/set-array.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/src/sourcemap-segment.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/src/types.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/set-array.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/set-array.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/set-array.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/set-array.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/types.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/types.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/types.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/gen-mapping/types/types.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/LICENSE"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/README.md"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/dist/remapping.mjs"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/dist/remapping.mjs.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/dist/remapping.umd.js"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/dist/remapping.umd.js.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/package.json"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/src/build-source-map-tree.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/src/remapping.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/src/source-map-tree.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/src/source-map.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/src/types.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/remapping.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/remapping.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/remapping.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/remapping.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/source-map-tree.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/source-map-tree.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/source-map-tree.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/source-map-tree.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/source-map.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/source-map.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/source-map.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/source-map.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/types.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/types.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/types.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping/types/types.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/LICENSE"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/README.md"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.mjs"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.mjs.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.umd.js"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.umd.js.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/package.json"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/src/binary-search.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/src/by-source.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/src/flatten-map.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/src/resolve.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/src/sort.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/src/sourcemap-segment.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/src/strip-filename.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/src/trace-mapping.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/src/types.ts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/binary-search.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/binary-search.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/binary-search.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/binary-search.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/by-source.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/by-source.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/by-source.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/by-source.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/resolve.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/resolve.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/resolve.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/resolve.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/sort.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/sort.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/sort.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/sort.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/types.d.cts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/types.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/types.d.mts"
Reading "node_modules/.pnpm/@jridgewell+remapping@2.3.5/node_modules/@jridgewell/trace-mapping/types/types.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri/LICENSE"
Reading "node_modules/.pnpm/@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri/README.md"
Reading "node_modules/.pnpm/@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.mjs"
Reading "node_modules/.pnpm/@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.mjs.map"
Reading "node_modules/.pnpm/@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.umd.js"
Reading "node_modules/.pnpm/@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.umd.js.map"
Reading "node_modules/.pnpm/@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri/dist/types/resolve-uri.d.ts"
Reading "node_modules/.pnpm/@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri/package.json"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/resolve-uri/LICENSE"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/resolve-uri/README.md"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.mjs"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.mjs.map"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.umd.js"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.umd.js.map"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/resolve-uri/dist/types/resolve-uri.d.ts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/resolve-uri/package.json"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/LICENSE"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/README.md"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js.map"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/package.json"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/src/scopes.ts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/src/sourcemap-codec.ts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/src/strings.ts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/src/vlq.ts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.cts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.mts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.cts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.mts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/strings.d.cts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/strings.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/strings.d.mts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/strings.d.mts.map"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.cts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.cts.map"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.mts"
Reading "node_modules/.pnpm/@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.mts.map"
Reading "node_modules/.pnpm/@parcel+watcher-darwin-arm64@2.6.0/node_modules/@parcel/watcher-darwin-arm64/LICENSE"
Reading "node_modules/.pnpm/@parcel+watcher-darwin-arm64@2.6.0/node_modules/@parcel/watcher-darwin-arm64/README.md"
Reading "node_modules/.pnpm/@parcel+watcher-darwin-arm64@2.6.0/node_modules/@parcel/watcher-darwin-arm64/package.json"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher-darwin-arm64/LICENSE"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher-darwin-arm64/README.md"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher-darwin-arm64/package.json"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/LICENSE"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/README.md"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/binding.gyp"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/index.d.ts"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/index.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/index.js.flow"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/package.json"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/scripts/build-from-source.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/Backend.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/Backend.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/Debounce.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/Debounce.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/DirTree.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/DirTree.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/Event.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/Glob.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/Glob.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/PromiseRunner.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/Signal.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/Watcher.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/Watcher.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/binding.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/kqueue/KqueueBackend.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/kqueue/KqueueBackend.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/linux/InotifyBackend.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/linux/InotifyBackend.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/macos/FSEventsBackend.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/macos/FSEventsBackend.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/shared/BruteForceBackend.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/shared/BruteForceBackend.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/unix/fts.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/unix/legacy.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/wasm/WasmBackend.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/wasm/WasmBackend.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/wasm/include.h"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/watchman/BSER.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/watchman/BSER.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/watchman/IPC.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/watchman/WatchmanBackend.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/watchman/WatchmanBackend.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/windows/WindowsBackend.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/windows/WindowsBackend.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/windows/win_utils.cc"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/src/windows/win_utils.hh"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/@parcel/watcher/wrapper.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/detect-libc/LICENSE"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/detect-libc/README.md"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/detect-libc/index.d.ts"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/detect-libc/lib/detect-libc.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/detect-libc/lib/elf.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/detect-libc/lib/filesystem.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/detect-libc/lib/process.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/detect-libc/package.json"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/is-glob/LICENSE"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/is-glob/README.md"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/is-glob/index.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/is-glob/package.json"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/LICENSE.md"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/README.md"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/common.gypi"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/except.gypi"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/index.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/napi-inl.deprecated.h"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/napi-inl.h"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/napi.h"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/node_addon_api.gyp"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/node_api.gyp"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/noexcept.gypi"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/nothing.c"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/package-support.json"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/package.json"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/tools/README.md"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/tools/check-napi.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/tools/clang-format.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/tools/conversion.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/node-addon-api/tools/eslint-format.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/picomatch/LICENSE"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/picomatch/README.md"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/picomatch/index.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/picomatch/lib/constants.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/picomatch/lib/parse.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/picomatch/lib/picomatch.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/picomatch/lib/scan.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/picomatch/lib/utils.js"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/picomatch/package.json"
Reading "node_modules/.pnpm/@parcel+watcher@2.6.0/node_modules/picomatch/posix.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/graceful-fs/LICENSE"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/graceful-fs/README.md"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/graceful-fs/clone.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/graceful-fs/graceful-fs.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/graceful-fs/legacy-streams.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/graceful-fs/package.json"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/graceful-fs/polyfills.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/LICENSE"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/README.md"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/AsyncParallelBailHook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/AsyncParallelHook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/AsyncSeriesBailHook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/AsyncSeriesHook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/AsyncSeriesLoopHook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/AsyncSeriesWaterfallHook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/Hook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/HookCodeFactory.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/HookMap.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/MultiHook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/SyncBailHook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/SyncHook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/SyncLoopHook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/SyncWaterfallHook.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/index.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/lib/util-browser.js"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/package.json"
Reading "node_modules/.pnpm/enhanced-resolve@5.24.5/node_modules/tapable/tapable.d.ts"
Reading "node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/LICENSE"
Reading "node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/README.md"
Reading "node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/clone.js"
Reading "node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/graceful-fs.js"
Reading "node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/legacy-streams.js"
Reading "node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/package.json"
Reading "node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/polyfills.js"
Reading "node_modules/.pnpm/is-extglob@2.1.1/node_modules/is-extglob/LICENSE"
Reading "node_modules/.pnpm/is-extglob@2.1.1/node_modules/is-extglob/README.md"
Reading "node_modules/.pnpm/is-extglob@2.1.1/node_modules/is-extglob/index.js"
Reading "node_modules/.pnpm/is-extglob@2.1.1/node_modules/is-extglob/package.json"
Reading "node_modules/.pnpm/is-glob@4.0.3/node_modules/is-extglob/LICENSE"
Reading "node_modules/.pnpm/is-glob@4.0.3/node_modules/is-extglob/README.md"
Reading "node_modules/.pnpm/is-glob@4.0.3/node_modules/is-extglob/index.js"
Reading "node_modules/.pnpm/is-glob@4.0.3/node_modules/is-extglob/package.json"
Reading "node_modules/.pnpm/is-glob@4.0.3/node_modules/is-glob/LICENSE"
Reading "node_modules/.pnpm/is-glob@4.0.3/node_modules/is-glob/README.md"
Reading "node_modules/.pnpm/is-glob@4.0.3/node_modules/is-glob/index.js"
Reading "node_modules/.pnpm/is-glob@4.0.3/node_modules/is-glob/package.json"
Reading "node_modules/.pnpm/lightningcss-darwin-arm64@1.33.0/node_modules/lightningcss-darwin-arm64/LICENSE"
Reading "node_modules/.pnpm/lightningcss-darwin-arm64@1.33.0/node_modules/lightningcss-darwin-arm64/README.md"
Reading "node_modules/.pnpm/lightningcss-darwin-arm64@1.33.0/node_modules/lightningcss-darwin-arm64/package.json"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/detect-libc/LICENSE"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/detect-libc/README.md"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/detect-libc/index.d.ts"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/detect-libc/lib/detect-libc.js"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/detect-libc/lib/elf.js"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/detect-libc/lib/filesystem.js"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/detect-libc/lib/process.js"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/detect-libc/package.json"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss-darwin-arm64/LICENSE"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss-darwin-arm64/README.md"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss-darwin-arm64/package.json"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/LICENSE"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/README.md"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/node/ast.d.ts"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/node/ast.js.flow"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/node/browserslistToTargets.js"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/node/composeVisitors.js"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/node/flags.js"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/node/index.d.ts"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/node/index.js"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/node/index.js.flow"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/node/index.mjs"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/node/targets.d.ts"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/node/targets.js.flow"
Reading "node_modules/.pnpm/lightningcss@1.33.0/node_modules/lightningcss/package.json"
Reading "node_modules/.pnpm/lock.yaml"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/LICENSE"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/README.md"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js.map"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/package.json"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/src/scopes.ts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/src/sourcemap-codec.ts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/src/strings.ts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/src/vlq.ts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.cts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.cts.map"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.mts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.mts.map"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.cts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.cts.map"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.mts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.mts.map"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/strings.d.cts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/strings.d.cts.map"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/strings.d.mts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/strings.d.mts.map"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.cts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.cts.map"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.mts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.mts.map"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/magic-string/LICENSE"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/magic-string/README.md"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/magic-string/dist/index.d.mts"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/magic-string/dist/index.mjs"
Reading "node_modules/.pnpm/magic-string@1.1.0/node_modules/magic-string/package.json"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/LICENSE"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/README.md"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.mjs"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.mjs.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.umd.js"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.umd.js.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/dist/types/gen-mapping.d.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/dist/types/set-array.d.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/dist/types/sourcemap-segment.d.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/dist/types/types.d.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/package.json"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/src/gen-mapping.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/src/set-array.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/src/sourcemap-segment.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/src/types.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/set-array.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/set-array.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/set-array.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/set-array.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/types.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/types.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/types.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/gen-mapping/types/types.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/LICENSE"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/README.md"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/dist/remapping.mjs"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/dist/remapping.mjs.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/dist/remapping.umd.js"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/dist/remapping.umd.js.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/package.json"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/src/build-source-map-tree.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/src/remapping.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/src/source-map-tree.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/src/source-map.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/src/types.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/build-source-map-tree.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/remapping.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/remapping.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/remapping.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/remapping.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/source-map-tree.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/source-map-tree.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/source-map-tree.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/source-map-tree.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/source-map.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/source-map.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/source-map.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/source-map.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/types.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/types.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/types.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/remapping/types/types.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/resolve-uri/LICENSE"
Reading "node_modules/.pnpm/node_modules/@jridgewell/resolve-uri/README.md"
Reading "node_modules/.pnpm/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.mjs"
Reading "node_modules/.pnpm/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.mjs.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.umd.js"
Reading "node_modules/.pnpm/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.umd.js.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/resolve-uri/dist/types/resolve-uri.d.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/resolve-uri/package.json"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/LICENSE"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/README.md"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/package.json"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/src/scopes.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/src/sourcemap-codec.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/src/strings.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/src/vlq.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/strings.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/strings.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/strings.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/strings.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/LICENSE"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/README.md"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.mjs"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.mjs.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.umd.js"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.umd.js.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/package.json"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/src/binary-search.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/src/by-source.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/src/flatten-map.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/src/resolve.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/src/sort.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/src/sourcemap-segment.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/src/strip-filename.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/src/trace-mapping.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/src/types.ts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/binary-search.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/binary-search.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/binary-search.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/binary-search.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/by-source.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/by-source.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/by-source.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/by-source.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/resolve.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/resolve.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/resolve.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/resolve.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/sort.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/sort.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/sort.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/sort.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/types.d.cts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/types.d.cts.map"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/types.d.mts"
Reading "node_modules/.pnpm/node_modules/@jridgewell/trace-mapping/types/types.d.mts.map"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher-darwin-arm64/LICENSE"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher-darwin-arm64/README.md"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher-darwin-arm64/package.json"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/LICENSE"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/README.md"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/binding.gyp"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/index.d.ts"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/index.js"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/index.js.flow"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/package.json"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/scripts/build-from-source.js"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/Backend.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/Backend.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/Debounce.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/Debounce.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/DirTree.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/DirTree.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/Event.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/Glob.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/Glob.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/PromiseRunner.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/Signal.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/Watcher.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/Watcher.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/binding.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/kqueue/KqueueBackend.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/kqueue/KqueueBackend.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/linux/InotifyBackend.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/linux/InotifyBackend.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/macos/FSEventsBackend.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/macos/FSEventsBackend.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/shared/BruteForceBackend.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/shared/BruteForceBackend.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/unix/fts.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/unix/legacy.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/wasm/WasmBackend.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/wasm/WasmBackend.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/wasm/include.h"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/watchman/BSER.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/watchman/BSER.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/watchman/IPC.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/watchman/WatchmanBackend.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/watchman/WatchmanBackend.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/windows/WindowsBackend.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/windows/WindowsBackend.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/windows/win_utils.cc"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/src/windows/win_utils.hh"
Reading "node_modules/.pnpm/node_modules/@parcel/watcher/wrapper.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/node/LICENSE"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/node/README.md"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/node/dist/esm-cache.loader.d.mts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/node/dist/esm-cache.loader.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/node/dist/index.d.mts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/node/dist/index.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/node/dist/index.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/node/dist/index.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/node/dist/require-cache.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/node/dist/require-cache.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/node/package.json"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-darwin-arm64/LICENSE"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-darwin-arm64/README.md"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-darwin-arm64/package.json"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/README.md"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/LICENSE"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/README.md"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/dist/emnapi-core.cjs.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/dist/emnapi-core.cjs.min.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/dist/emnapi-core.cjs.min.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/dist/emnapi-core.d.mts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/dist/emnapi-core.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/dist/emnapi-core.esm-bundler.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/dist/emnapi-core.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/dist/emnapi-core.min.d.mts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/dist/emnapi-core.min.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/dist/emnapi-core.min.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/dist/emnapi-core.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/index.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core/package.json"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/LICENSE"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/README.md"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.cjs.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.cjs.min.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.cjs.min.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.d.mts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.esm-bundler.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.iife.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.iife.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.min.d.mts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.min.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.min.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/dist/emnapi.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/index.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime/package.json"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/LICENSE"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/README.md"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/dist/wasi-threads.cjs.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/dist/wasi-threads.cjs.min.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/dist/wasi-threads.cjs.min.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/dist/wasi-threads.d.mts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/dist/wasi-threads.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/dist/wasi-threads.esm-bundler.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/dist/wasi-threads.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/dist/wasi-threads.min.d.mts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/dist/wasi-threads.min.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/dist/wasi-threads.min.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/dist/wasi-threads.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/index.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads/package.json"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime/dist/emnapi-plugins.cjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime/dist/emnapi-plugins.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime/dist/fs-proxy.cjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime/dist/fs.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime/fs-proxy.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime/package.json"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime/runtime.cjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime/runtime.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/README.md"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/dist/tsdoc-metadata.json"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/dist/wasm-util.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/dist/wasm-util.esm-bundler.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/dist/wasm-util.esm.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/dist/wasm-util.esm.min.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/dist/wasm-util.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/dist/wasm-util.min.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/asyncify.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/index.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/jspi.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/load.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/memory.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/wasi/error.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/wasi/fd.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/wasi/fs.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/wasi/index.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/wasi/path.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/wasi/preview1.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/wasi/rights.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/wasi/types.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/wasi/util.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/cjs/webassembly.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/asyncify.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/index.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/jspi.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/load.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/memory.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/wasi/error.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/wasi/fd.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/wasi/fs.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/wasi/index.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/wasi/path.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/wasi/preview1.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/wasi/rights.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/wasi/types.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/wasi/util.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/lib/mjs/webassembly.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util/package.json"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/CopyrightNotice.txt"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/LICENSE.txt"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/README.md"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/SECURITY.md"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/modules/index.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/modules/index.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/modules/package.json"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/package.json"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/tslib.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/tslib.es6.html"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/tslib.es6.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/tslib.es6.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/tslib.html"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib/tslib.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/package.json"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/tailwindcss-oxide.wasi-browser.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/tailwindcss-oxide.wasi.cjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/wasi-worker-browser.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide-wasm32-wasi/wasi-worker.mjs"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide/LICENSE"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide/index.d.ts"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide/index.js"
Reading "node_modules/.pnpm/node_modules/@tailwindcss/oxide/package.json"
Reading "node_modules/.pnpm/node_modules/detect-libc/LICENSE"
Reading "node_modules/.pnpm/node_modules/detect-libc/README.md"
Reading "node_modules/.pnpm/node_modules/detect-libc/index.d.ts"
Reading "node_modules/.pnpm/node_modules/detect-libc/lib/detect-libc.js"
Reading "node_modules/.pnpm/node_modules/detect-libc/lib/elf.js"
Reading "node_modules/.pnpm/node_modules/detect-libc/lib/filesystem.js"
Reading "node_modules/.pnpm/node_modules/detect-libc/lib/process.js"
Reading "node_modules/.pnpm/node_modules/detect-libc/package.json"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/LICENSE"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/README.md"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/AliasFieldPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/AliasPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/AliasUtils.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/AppendPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/CloneBasenamePlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/ConditionalPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/DescriptionFilePlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/DescriptionFileUtils.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/DirectoryExistsPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/ExportsFieldPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/ExtensionAliasPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/FileExistsPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/ImportsFieldPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/JoinRequestPartPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/JoinRequestPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/LogInfoPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/MainFieldPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/ModulesInHierachicDirectoriesPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/ModulesInHierarchicalDirectoriesPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/ModulesInRootPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/ModulesUtils.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/NextPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/ParsePlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/PnpPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/Resolver.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/ResolverFactory.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/RestrictionsPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/ResultPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/RootsPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/SelfReferencePlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/SymlinkPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/SyncAsyncFileSystemDecorator.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/TryNextPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/TsconfigPathsPlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/UnsafeCachePlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/UseFilePlugin.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/createInnerContext.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/forEachBail.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/getInnerRequest.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/getPaths.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/index.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/entrypoints.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/fileURLToPath.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/fs.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/graceful-fs-browser.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/identifier.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/memoize.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/module-browser.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/path-browser.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/path.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/process-browser.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/strip-json-comments.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/lib/util/url-browser.js"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/package.json"
Reading "node_modules/.pnpm/node_modules/enhanced-resolve/types.d.ts"
Reading "node_modules/.pnpm/node_modules/graceful-fs/LICENSE"
Reading "node_modules/.pnpm/node_modules/graceful-fs/README.md"
Reading "node_modules/.pnpm/node_modules/graceful-fs/clone.js"
Reading "node_modules/.pnpm/node_modules/graceful-fs/graceful-fs.js"
Reading "node_modules/.pnpm/node_modules/graceful-fs/legacy-streams.js"
Reading "node_modules/.pnpm/node_modules/graceful-fs/package.json"
Reading "node_modules/.pnpm/node_modules/graceful-fs/polyfills.js"
Reading "node_modules/.pnpm/node_modules/is-extglob/LICENSE"
Reading "node_modules/.pnpm/node_modules/is-extglob/README.md"
Reading "node_modules/.pnpm/node_modules/is-extglob/index.js"
Reading "node_modules/.pnpm/node_modules/is-extglob/package.json"
Reading "node_modules/.pnpm/node_modules/is-glob/LICENSE"
Reading "node_modules/.pnpm/node_modules/is-glob/README.md"
Reading "node_modules/.pnpm/node_modules/is-glob/index.js"
Reading "node_modules/.pnpm/node_modules/is-glob/package.json"
Reading "node_modules/.pnpm/node_modules/jiti/LICENSE"
Reading "node_modules/.pnpm/node_modules/jiti/README.md"
Reading "node_modules/.pnpm/node_modules/jiti/dist/babel.cjs"
Reading "node_modules/.pnpm/node_modules/jiti/dist/jiti.cjs"
Reading "node_modules/.pnpm/node_modules/jiti/lib/jiti-cli.mjs"
Reading "node_modules/.pnpm/node_modules/jiti/lib/jiti-hooks.mjs"
Reading "node_modules/.pnpm/node_modules/jiti/lib/jiti-native.mjs"
Reading "node_modules/.pnpm/node_modules/jiti/lib/jiti-register.d.mts"
Reading "node_modules/.pnpm/node_modules/jiti/lib/jiti-register.mjs"
Reading "node_modules/.pnpm/node_modules/jiti/lib/jiti-static.mjs"
Reading "node_modules/.pnpm/node_modules/jiti/lib/jiti.cjs"
Reading "node_modules/.pnpm/node_modules/jiti/lib/jiti.d.cts"
Reading "node_modules/.pnpm/node_modules/jiti/lib/jiti.d.mts"
Reading "node_modules/.pnpm/node_modules/jiti/lib/jiti.mjs"
Reading "node_modules/.pnpm/node_modules/jiti/lib/types.d.ts"
Reading "node_modules/.pnpm/node_modules/jiti/package.json"
Reading "node_modules/.pnpm/node_modules/lightningcss-darwin-arm64/LICENSE"
Reading "node_modules/.pnpm/node_modules/lightningcss-darwin-arm64/README.md"
Reading "node_modules/.pnpm/node_modules/lightningcss-darwin-arm64/package.json"
Reading "node_modules/.pnpm/node_modules/lightningcss/LICENSE"
Reading "node_modules/.pnpm/node_modules/lightningcss/README.md"
Reading "node_modules/.pnpm/node_modules/lightningcss/node/ast.d.ts"
Reading "node_modules/.pnpm/node_modules/lightningcss/node/ast.js.flow"
Reading "node_modules/.pnpm/node_modules/lightningcss/node/browserslistToTargets.js"
Reading "node_modules/.pnpm/node_modules/lightningcss/node/composeVisitors.js"
Reading "node_modules/.pnpm/node_modules/lightningcss/node/flags.js"
Reading "node_modules/.pnpm/node_modules/lightningcss/node/index.d.ts"
Reading "node_modules/.pnpm/node_modules/lightningcss/node/index.js"
Reading "node_modules/.pnpm/node_modules/lightningcss/node/index.js.flow"
Reading "node_modules/.pnpm/node_modules/lightningcss/node/index.mjs"
Reading "node_modules/.pnpm/node_modules/lightningcss/node/targets.d.ts"
Reading "node_modules/.pnpm/node_modules/lightningcss/node/targets.js.flow"
Reading "node_modules/.pnpm/node_modules/lightningcss/package.json"
Reading "node_modules/.pnpm/node_modules/magic-string/LICENSE"
Reading "node_modules/.pnpm/node_modules/magic-string/README.md"
Reading "node_modules/.pnpm/node_modules/magic-string/dist/index.d.mts"
Reading "node_modules/.pnpm/node_modules/magic-string/dist/index.mjs"
Reading "node_modules/.pnpm/node_modules/magic-string/package.json"
Reading "node_modules/.pnpm/node_modules/mri/index.d.ts"
Reading "node_modules/.pnpm/node_modules/mri/lib/index.js"
Reading "node_modules/.pnpm/node_modules/mri/lib/index.mjs"
Reading "node_modules/.pnpm/node_modules/mri/license.md"
Reading "node_modules/.pnpm/node_modules/mri/package.json"
Reading "node_modules/.pnpm/node_modules/mri/readme.md"
Reading "node_modules/.pnpm/node_modules/node-addon-api/LICENSE.md"
Reading "node_modules/.pnpm/node_modules/node-addon-api/README.md"
Reading "node_modules/.pnpm/node_modules/node-addon-api/common.gypi"
Reading "node_modules/.pnpm/node_modules/node-addon-api/except.gypi"
Reading "node_modules/.pnpm/node_modules/node-addon-api/index.js"
Reading "node_modules/.pnpm/node_modules/node-addon-api/napi-inl.deprecated.h"
Reading "node_modules/.pnpm/node_modules/node-addon-api/napi-inl.h"
Reading "node_modules/.pnpm/node_modules/node-addon-api/napi.h"
Reading "node_modules/.pnpm/node_modules/node-addon-api/node_addon_api.gyp"
Reading "node_modules/.pnpm/node_modules/node-addon-api/node_api.gyp"
Reading "node_modules/.pnpm/node_modules/node-addon-api/noexcept.gypi"
Reading "node_modules/.pnpm/node_modules/node-addon-api/nothing.c"
Reading "node_modules/.pnpm/node_modules/node-addon-api/package-support.json"
Reading "node_modules/.pnpm/node_modules/node-addon-api/package.json"
Reading "node_modules/.pnpm/node_modules/node-addon-api/tools/README.md"
Reading "node_modules/.pnpm/node_modules/node-addon-api/tools/check-napi.js"
Reading "node_modules/.pnpm/node_modules/node-addon-api/tools/clang-format.js"
Reading "node_modules/.pnpm/node_modules/node-addon-api/tools/conversion.js"
Reading "node_modules/.pnpm/node_modules/node-addon-api/tools/eslint-format.js"
Reading "node_modules/.pnpm/node_modules/picocolors/LICENSE"
Reading "node_modules/.pnpm/node_modules/picocolors/README.md"
Reading "node_modules/.pnpm/node_modules/picocolors/package.json"
Reading "node_modules/.pnpm/node_modules/picocolors/picocolors.browser.js"
Reading "node_modules/.pnpm/node_modules/picocolors/picocolors.d.ts"
Reading "node_modules/.pnpm/node_modules/picocolors/picocolors.js"
Reading "node_modules/.pnpm/node_modules/picocolors/types.d.ts"
Reading "node_modules/.pnpm/node_modules/picomatch/LICENSE"
Reading "node_modules/.pnpm/node_modules/picomatch/README.md"
Reading "node_modules/.pnpm/node_modules/picomatch/index.js"
Reading "node_modules/.pnpm/node_modules/picomatch/lib/constants.js"
Reading "node_modules/.pnpm/node_modules/picomatch/lib/parse.js"
Reading "node_modules/.pnpm/node_modules/picomatch/lib/picomatch.js"
Reading "node_modules/.pnpm/node_modules/picomatch/lib/scan.js"
Reading "node_modules/.pnpm/node_modules/picomatch/lib/utils.js"
Reading "node_modules/.pnpm/node_modules/picomatch/package.json"
Reading "node_modules/.pnpm/node_modules/picomatch/posix.js"
Reading "node_modules/.pnpm/node_modules/source-map-js/LICENSE"
Reading "node_modules/.pnpm/node_modules/source-map-js/README.md"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/array-set.js"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/base64-vlq.js"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/base64.js"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/binary-search.js"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/mapping-list.js"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/quick-sort.js"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/source-map-consumer.d.ts"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/source-map-consumer.js"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/source-map-generator.d.ts"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/source-map-generator.js"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/source-node.d.ts"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/source-node.js"
Reading "node_modules/.pnpm/node_modules/source-map-js/lib/util.js"
Reading "node_modules/.pnpm/node_modules/source-map-js/package.json"
Reading "node_modules/.pnpm/node_modules/source-map-js/source-map.d.ts"
Reading "node_modules/.pnpm/node_modules/source-map-js/source-map.js"
Reading "node_modules/.pnpm/node_modules/tapable/LICENSE"
Reading "node_modules/.pnpm/node_modules/tapable/README.md"
Reading "node_modules/.pnpm/node_modules/tapable/lib/AsyncParallelBailHook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/AsyncParallelHook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/AsyncSeriesBailHook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/AsyncSeriesHook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/AsyncSeriesLoopHook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/AsyncSeriesWaterfallHook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/Hook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/HookCodeFactory.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/HookMap.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/MultiHook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/SyncBailHook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/SyncHook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/SyncLoopHook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/SyncWaterfallHook.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/index.js"
Reading "node_modules/.pnpm/node_modules/tapable/lib/util-browser.js"
Reading "node_modules/.pnpm/node_modules/tapable/package.json"
Reading "node_modules/.pnpm/node_modules/tapable/tapable.d.ts"
Reading "node_modules/@tailwindcss/cli/LICENSE"
Reading "node_modules/@tailwindcss/cli/README.md"
Reading "node_modules/@tailwindcss/cli/dist/index.mjs"
Reading "node_modules/@tailwindcss/cli/package.json"
Reading "node_modules/my-ui-lib/index.html"
Reading "node_modules/tailwindcss/LICENSE"
Reading "node_modules/tailwindcss/README.md"
Reading "node_modules/tailwindcss/dist/chunk-5JIJA4QV.mjs"
Reading "node_modules/tailwindcss/dist/chunk-C2OYBFIH.mjs"
Reading "node_modules/tailwindcss/dist/chunk-DCG7AFIE.mjs"
Reading "node_modules/tailwindcss/dist/colors-C__qRT83.d.ts"
Reading "node_modules/tailwindcss/dist/colors.d.mts"
Reading "node_modules/tailwindcss/dist/colors.d.ts"
Reading "node_modules/tailwindcss/dist/colors.js"
Reading "node_modules/tailwindcss/dist/colors.mjs"
Reading "node_modules/tailwindcss/dist/default-theme.d.mts"
Reading "node_modules/tailwindcss/dist/default-theme.d.ts"
Reading "node_modules/tailwindcss/dist/default-theme.js"
Reading "node_modules/tailwindcss/dist/default-theme.mjs"
Reading "node_modules/tailwindcss/dist/flatten-color-palette.d.mts"
Reading "node_modules/tailwindcss/dist/flatten-color-palette.d.ts"
Reading "node_modules/tailwindcss/dist/flatten-color-palette.js"
Reading "node_modules/tailwindcss/dist/flatten-color-palette.mjs"
Reading "node_modules/tailwindcss/dist/lib.d.mts"
Reading "node_modules/tailwindcss/dist/lib.d.ts"
Reading "node_modules/tailwindcss/dist/lib.js"
Reading "node_modules/tailwindcss/dist/lib.mjs"
Reading "node_modules/tailwindcss/dist/plugin.d.mts"
Reading "node_modules/tailwindcss/dist/plugin.d.ts"
Reading "node_modules/tailwindcss/dist/plugin.js"
Reading "node_modules/tailwindcss/dist/plugin.mjs"
Reading "node_modules/tailwindcss/dist/resolve-config-B4yBzhca.d.ts"
Reading "node_modules/tailwindcss/dist/resolve-config-QUZ9b-Gn.d.mts"
Reading "node_modules/tailwindcss/dist/types-DWdTiksJ.d.mts"
Reading "node_modules/tailwindcss/package.json"
Reading "package.json"
Reading "pnpm-lock.yaml"
Reading "pnpm-workspace.yaml"
Reading "public/index.html"
Reading "tailwindcss-10742.log"
```

</details>

<details>

<summary>After</summary>

```
Reading "./app/index.html"
Reading "./package.json"
Reading "./public/index.html"
```

</details>
2026-08-07 17:44:33 +02:00
Robin Malfait
e566a926e7
--default(…) should emit values as-is (#20392)
This PR fixes an issue where the `--default(…)` function that can be
used as part of `@utility` definitions goes through the same
pre-processing treatment as `--value(…)` and `--modifier(…)`.

Instead the `--default(…)` value should be emitted as-is.

The pre-processing treatment that's in place today is just to normalize
the value(s) that could be formatted in a strange way if you are dealing
with older Prettier / Biome versions.

Some of the transformations we do ahead of time:

- `--value(--spacing)`                 -> `--value(--spacing-*)`
- `--value(--spacing- *)`              -> `--value(--spacing-*)`
- `--value(--text- * --line-height)` -> `--value(--text-*--line-height)`
- `--value(--text --line-height)` -> `--value(--text-*--line-height)`
- `--value(--text-\\* --line-height)` ->
`--value(--text-*--line-height)`
- `--value([ *])`                      -> `--value([*])`


One of these steps is getting rid of the spaces, that means that if your
`--default(…)` uses spaces, then we will get rid of it.

```css
@theme inline {
  --text-box-trim-start: trim-start;
  --text-box-trim-end: trim-end;
  --text-box-trim-both: trim-both;

  --text-box-edge-cap: cap alphabetic;
  --text-box-edge-cap-text: cap text;
  --text-box-edge-ex: ex alphabetic;
  --text-box-edge-ex-text: ex text;
  --text-box-edge-text: text alphabetic;
  --text-box-edge-text-text: text text;
}
@utility trim-* {
  text-box: --value('normal', 'inherit', 'initial', 'revert', 'revert-layer', 'unset');
  text-box: --value(--text-box-trim-*) --modifier(--text-box-edge-*, --default(box alphabetic));
}
```

When you try to use this:

```html
<div className="trim-both" />
```

Then we get the following CSS out:
```css
.trim-both {
 text-box: trim-both boxalphabetic;
}
```

This PR fixes that, and now the proper CSS will be emitted:
```css
.trim-both {
 text-box: trim-both box alphabetic;
}
```

Fixes: #20391

## Test plan

1. Added an regression test to make sure that `--default(…)` values are
emitted as-is.
2026-08-06 15:25:48 +02:00
Lazizbek Ergashev
b8a5f85889
Detect classes in Ruby percent literals using angle brackets or custom delimiters (#20387)
## Summary

Fixes #20386.

Ruby lets you close a `%w`/`%W` literal with any non-alphanumeric
character, but the pre-processor only recognized `[`, `(`, `{`, `#` and
a space. So `%w<bg-green-400>` and `%w|bg-blue-400|` were skipped and
the classes inside them never made it into the output.

The boundary lookup now maps `<` to `>` alongside the other paired
delimiters, and falls back to treating any other non-alphanumeric,
non-whitespace character as its own closing delimiter.

## Test plan

Added pre-processor and extraction cases for `%w<…>`, `%w|…|`, `%w:…:`
and `%w!…!` in `crates/oxide/src/extractor/pre_processors/ruby.rs`. They
fail on `main` and pass with this change.

```
cargo test -p tailwindcss-oxide
```

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-08-06 14:52:04 +02:00
Robin Malfait
3524b45310
Attempt to fix flaky integration test (#20384)
One of the integration tests is flaky on CI. This is an attempt to "fix"
it.

It has probably something to do with the availability of the resources
provided by GitHub because locally this works flawlessly for 100 runs
straight.

## Test plan

- Once we get 5 consecutive positive runs we van merge it.
- No actual code was changed, only the flaky integration test itself.

<img width="336" height="445" alt="image"
src="https://github.com/user-attachments/assets/bec81687-62f5-4df3-8109-d0aaa4ffbd8a"
/>
2026-08-05 12:39:27 +02:00
Robin Malfait
b5ab20473b
improve flaky integration test 2026-08-04 19:27:26 +02:00
Robin Malfait
9dae5163db
update changelog 2026-08-04 18:42:00 +02:00
Robin Malfait
d190343594
Use wasm as a fallback for @tailwindcss/oxide (#20383)
Right now, we use Rust for `@tailwindcss/oxide` which has 2
responsibilities:

1. Traverse the file system and figure out which files need to be
scanned based on auto source detection and `@source` directives.
2. Given those files, extract possible Tailwind CSS classes which we
call candidates.

Since this is using native code, we use napi-rs to get native `.node`
files on a per platform / arch basis.

So far so good, however, if you are on an OS that doesn't have a
prebuilt binary, you will receive an error that might look like this:

```
Error: Cannot find native binding. npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). Please try `npm i` again after removing both package-lock.json and node_modules directory.
    at Object.<anonymous> (/private/var/folders/1k/bdv8blv93xq7qgwjdwc9z88h0000gn/T/tailwind-integrationspYHIVP/node_modules/.pnpm/@tailwindcss+oxide@file+..+..+..+..+..+..+..+Users+robin+github.com+tailwindlabs+tailwi_47ae1688f61c719f66c73e2ff35e430f/node_modules/@tailwindcss/oxide/index.js:573:19)
    at Module._compile (node:internal/modules/cjs/loader:1829:14)
    at Object..js (node:internal/modules/cjs/loader:1969:10)
    at Module.load (node:internal/modules/cjs/loader:1552:32)
    at Module._load (node:internal/modules/cjs/loader:1354:12)
    at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
    at Module.require (node:internal/modules/cjs/loader:1575:12)
    at require (node:internal/modules/helpers:191:16)
    at file:///private/var/folders/1k/bdv8blv93xq7qgwjdwc9z88h0000gn/T/tailwind-integrationspYHIVP/index.mjs:5:19
    at ModuleJob.run (node:internal/modules/esm/module_job:437:25) {
  cause: Error: Cannot find module '@tailwindcss/oxide-darwin-arm64'
```

This means that we have to add support for these platforms, and there
are some open PRs related to this, which could be closed by this PR:

- #20327
- #20276
- #20201

Today we already have support for the big platforms out there:

- Windows arm64
- Windows x64
- macOS arm64
- macOS x64

But then it starts to get a bit out of hand once we start looking at
Linux based versions:

- Android arm eabi
- Android arm64
- Linux arm64 gnu
- Linux arm64 gnueabihf
- Linux arm64 musl
- Linux x64 gnu
- Linux x64 musl
- freebsd x64

... and then we have the pending list from the 3 PRs linked above.
Adding support for all of these is not the end of the world, but it gets
complex if we need to keep supporting more and more. Right now we rely
on a bunch of non-default napi-rs setup in CI just to support these
other platforms.

This PR solves that by using the `wasm32-wasi` build as a universal
fallback. The napi-rs generated loader already knows how to fall back to
`@tailwindcss/oxide-wasm32-wasi`, but that package declared `"cpu":
["wasm32"]`, so npm/pnpm never installed it on real hardware. Removing
that restriction means the package is installed everywhere, and the
loader picks it up whenever no native binding exists.

This PR also fixes a `UVWASI_EACCES` crash on sandboxed platforms
(OpenHarmony, Android): the generated wasm loader preopens `/`, which
those sandboxes deny, so the fallback failed to load on exactly the
platforms that need it (see [this
comment](https://github.com/tailwindlabs/tailwindcss/pull/20276#issuecomment-4950167198)).
We patch `@napi-rs/cli`'s codegen templates via `pnpm patch` to retry
with narrower preopens (`/` → cwd → none). On such platforms, scanning
is limited to files under the current working directory.

## Test plan

Added two integration tests:

1. Trick pnpm (via `supportedArchitectures`) into installing for a
platform we explicitly don't support, and assert `@tailwindcss/oxide`
loads the wasm binding and scans files from disk.
2. Simulate a sandbox that denies preopening `/`, and assert the wasm
binding still loads and scans.

[ci-all]
2026-08-04 18:41:43 +02:00
Robin Malfait
05c3a87627
Bump dependencies (#20381)
This PR bumps dependencies and dev dependencies and applies required
changes due to version bumps.

- Moved `@parcel/watcher` related dependencies to the
`pnpm-workflow.yaml` catalog
- Bumped Lightning CSS + updated tests with improvements
- Bumped napi related dependencies
- Tackled a Vitest warning

## Test plan

All tests should still pass on all platforms [ci-all]
2026-08-04 13:55:03 +02:00
Lazizbek Ergashev
50daebded0
Prevent @tailwindcss/vite crash under Vite's experimental bundledDev (#20379)
## Summary

Under Vite's experimental `bundledDev` mode, the `hotUpdate` hook in
`@tailwindcss/vite` gets called without a `server`. Vite only passes `{
type, file, modules }` here, but the hook loops over
`Object.values(server.environments)`, so editing any file (JS, CSS, or
HTML) throws `TypeError: Cannot read properties of undefined (reading
'environments')` and the dev server build fails.

The fix returns early when `server` is missing. Those environment loops
only look at environments other than the current one, and the
server-level `hot`/`ws` reload channels don't exist in this mode, so
bailing out leaves the classic (non-`bundledDev`) dev path untouched.

Fixes #20378

## Test plan

- Added a unit test that calls `hotUpdate` without a `server` and checks
it doesn't throw. It fails on the current code and passes with the
guard.
- Reproduced with a Vite 8 project using `experimental.bundledDev:
true`: before the change, editing any JS/CSS/HTML file crashed the dev
server; after it, edits work.
- `pnpm run test` and the `@tailwindcss/vite` integration suite both
pass.


[ci-all]

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-08-03 15:10:06 +02:00
Robin Malfait
6def8205b5 update changelog 2026-08-03 13:11:43 +02:00
Robin Malfait
a4e0f75633 try converting variants with arbitrary values to builtin variants 2026-08-03 13:11:43 +02:00
Robin Malfait
489c22b4be add failing tests 2026-08-03 13:11:43 +02:00
Robin Malfait
4be6110aff update changelog 2026-07-31 15:13:38 +02:00
Tim Neutkens
25fa72c1ec Use default Turbopack mode in integration test 2026-07-31 15:13:38 +02:00
Tim Neutkens
56565b68f9 Add Turbopack loader integration coverage 2026-07-31 15:13:38 +02:00
Tim Neutkens
c9689a44a6 Add @tailwindcss/turbopack loader 2026-07-31 15:13:38 +02:00
Robin Malfait
d494a78532 update changelog 2026-07-31 14:53:47 +02:00
Robin Malfait
78b4864d30 always emit , ) instead of ,) for fallback values in CSS variables
This will bring the development version and production version closer
together. It also means that the development version works in older
supported browsers like  Chrome v112 where the space is required (even
though the spec allows both)

There are no changes in tests just becaues we already run each test
through Lightning CSS which normalizes these values:

Input:
```css
var(--tw-blur,)
var(--tw-blur, )
```

Lightning CSS output:
```css
var(--tw-blur, )
var(--tw-blur, )
```
2026-07-31 14:53:47 +02:00
Robin Malfait
423aee5309
Improve handling of @scope related at-rules (#20369)
Continuation of #20344, original commits of that PR are present in this
PR as well.


Before we talk about the issues, there is some jargon I'll be using that
might be unfamiliar:
```css
@scope (.from) to (.to) {
  /*   ^^^^^^^^^^^^^^^^    This is the prelude */
  /*   ^^^^^^^             This is the `scope-start` */
  /*              ^^^^^    This is the `scope-end` */
}

/* The `scope-start` is optional, if you only want `scope-end`: */
@scope to (.to) {
}

/* The `scope-end` is optional, if you only want `scope-start`: */
@scope (.from) {
}
```

This PR contains multiple fixes, but they are all related to the
`@scope` at-rule. The `@scope` at-rule is a beast, it's an at-rule where
we do have multiple selectors in the prelude, which in turn can contain
`&` rules. If you use `&` inside of an `@scope` rule, its meaning
changes compared to what `&` typically means and it is even different
whether it exists in `scope-start` or `scope-end`.

Let's start with the original issue, if you define a custom variant
like:
```css
@custom-variant blue {
  @scope ([data-theme='blue']) to ([data-theme]) {
    @slot;
  }
}
```

Then using `blue:bg-blue-500` used to generate:

```css
.blue\:bg-blue-500 {
  @scope ([data-theme='blue']) to ([data-theme]) {
    background-color: var(--color-blue-500);
  }
}
```

This is because internally we start with the AST node representing
`bg-blue-500`, then we wrap all the `variant` related nodes around it
(`blue`), and then wrap everything in the final rule with a selector
representing the class (`.blue\:bg-blue-500`).

This is incorrect because you want that sandwich effect where any
`.blue\:bg-blue-500` class between an element with the
`[data-theme="blue"]` attribute and another nested `[data-theme]`
attribute.

The other [PR](#20344) solved this by simply making sure that the
`@scope` rule gets hoisted to the top. For this particular example that
was enough.

However, that would result in a ton more issues. This hoisting or
swapping with the rule is only "safe" in a `custom-variant`.

So similar to the other PR, this PR wraps this properly:

```css
@scope ([data-theme='blue']) to ([data-theme]) {
  .blue\:bg-blue-500 {
    background-color: var(--color-blue-500);
  }
}
```

The tricky part is that the nesting pass runs on _all_ CSS, custom
variants _and_ your own CSS. Hoisting an `@scope` rule out of the rule
it is nested in changes its meaning.

Take a look at this example:

```css
.parent {
  @scope (.from) to (.to) {
    * {
      border: 1px solid black;
    }
  }
}
```

This says that you need a structure roughly like this:
```html
<div class="parent">
  <div class="from">
    <div>This element will get a border</div>

    <div class="to">
      <div>This element won't get a border, it's beyond the scope</div>
    </div>
  </div>
</div>
```

If we had switched it:
```css
@scope (.from) to (.to) {
  .parent {
    * {
      border: 1px solid black;
    }
  }
}
```

Then this requires that an element with a class of `.parent` lives
between the
`.from` scope-start and `.to` scope-end:
```html
<div class="from">
  <div class="parent">
    <div>This element will get a border</div>

    <div class="to">
      <div>This element won't get a border, it's beyond the scope</div>
    </div>
  </div>
</div>
```

Subtle, but it's definitely wrong.

Instead, we will apply our flattening rules, such that the `@scope` gets
hoisted, but we have to bring that `.parent` selector requirement inside
of the `@scope` prelude, more specifically inside the `scope-start`:

```css
@scope (.parent .from) to (.to) {
  * {
    border: 1px solid black;
  }
}
```

We _could_ have left this as-is, but there is a Lightning CSS bug where
the original CSS:
```css
.parent {
  @scope (.from) to (.to) {
    * {
      border: 1px solid black;
    }
  }
}
```

gets turned into:
```css
@scope (.from) to (.to) {
  :scope * {
    border: 1px solid #000;
  }
}
```

Notice how the `.parent` class is not found at all? (I will open some
issues on the Lightning CSS repo (and PRs to fix this). But it's a good
fix to have in Tailwind CSS as well, just because a lot of tooling is
already using Lightning CSS on top of Tailwind CSS, so we need an entire
chain to be updated for this to work.)

Side note: the version we generate is untouched by Lightning CSS.

## Resolving `&` in the prelude

`&` resolves differently depending on which scope selector you're
looking at
([spec](https://drafts.csswg.org/css-nesting-1/#nesting-at-scope)):

- In the `<scope-start>` selector, `&` refers to the elements matched by
the nearest ancestor style rule (the utility, for variants).
- In the `<scope-end>` selector, `&` refers to the scoping root and
behaves like `:where(:scope)`. `:scope` might work, but that has a
specificity of `0,1,0` instead of `0,0,0`.

So this input:

```css
.parent {
  @scope (& > .scope) to (& .limit) {
    .content {
      color: red;
    }
  }
}
```

compiles to:

```css
@scope (.parent > .scope) to (:where(:scope) .limit) {
  .content {
    color: red;
  }
}
```

The substitution uses the exact same logic as `&` in nested style rule
selectors: `:is(…)` semantics by default, dropping the `:is(…)` whenever
the substitution is provably equivalent.

So a complex parent compiles to `@scope (.a .b > .from)` rather than
`@scope (:is(.a .b) > .from)`, while `.card&` inside a `main` rule keeps
it (`@scope (.card:is(main))`) because substituting the type selector
as-is would produce invalid CSS.

For user CSS, every selector in a `<scope-start>` selector list is
relative to the parent rule, whether it uses `&` or not, just like
nested style rule selectors:

```css
.parent {
  @scope (.a, & > .b) {
    .inside {
      color: red;
    }
  }
}
```

compiles to:

```css
@scope (.parent .a, .parent > .b) {
  .inside {
    color: red;
  }
}
```

## Controlling the composition with `&`

Because `&` in the `<scope-start>` selector refers to the utility,
variant authors can control where the utility ends up:

```css
/* Default: the utility applies to elements inside the scope */
@custom-variant in-blue {
  @scope ([data-theme='blue']) to ([data-theme]) {
    @slot;
  }
}

/* Trailing `&`: the utility itself becomes the scoping root */
@custom-variant blue-self {
  @scope ([data-theme='blue'] &) to ([data-theme]) {
    @slot;
  }
}

/* Leading `&`: the scoping roots are found inside the utility */
@custom-variant scoped-panel {
  @scope (& .panel) {
    @slot;
  }
}
```

generates:

```css
@scope ([data-theme='blue']) to ([data-theme]) {
  .in-blue\:flex {
    display: flex;
  }
}

@scope ([data-theme='blue'] .blue-self\:flex) to ([data-theme]) {
  :where(:scope) {
    display: flex;
  }
}

@scope (.scoped-panel\:flex .panel) {
  :where(:scope) {
    display: flex;
  }
}
```

Note that when the utility becomes the scoping root, the `<scope-end>`
limit no longer affects the utility itself (a scoping root is never
beyond its own limits), and the declarations apply to the root with zero
specificity. The default composition is usually what you want to get the
sandwich effect, but now you _can_ get the other behavior if you want.

## Bare declarations

There is another small Lightning CSS bug that we want to fix. If you
have the following CSS:
```css
@scope (.from) to (.to) {
  color: red;
}
```

Then Lightning CSS crashes with an unexpected input. The [spec
says](https://drafts.csswg.org/css-cascade-6/#scoped-declarations) that:

> Declarations may be used directly with the body of a `@scope` rule.
Contiguous runs of declarations are wrapped in nested declarations
rules, which match the scoping root with zero specificity.
>
> ```css
> @scope (.foo) {
>   border: 1px solid black;
> }
> ```
> is equivalent to:
> ```css
> scope (.foo) {
>   :where(:scope) {
>     border: 1px solid black;
>   }
> }
> ```

So this is what we will do as well:
```css
@scope (.from) to (.to) {
  :where(:scope) {
    color: red;
  }
}
```

---

One more small detail about the implementation: we do have to track
which CSS is coming from the custom variant, and what is user defined
CSS. To do this, we insert some internal `context` nodes with a `{
source: 'user' }` or `{ source: 'variant' }` so we know when we can just
swap this `@scope` around, or if we just have to handle the nesting and
`&` substitutions.

Fixes: #18961
Closes: #20344

## Test plan

1. Added loads of new tests related to `@scope` in user-land
1. Added loads of new tests related to `@scope` in custom variants
   - When using the `@custom-variant` shorthand syntax
   - When using the `@custom-variant` syntax with `@slot`
   - When using arbitrary variants like `[@scope_(.from)_to_(.to)]:flex`
   - When using `addVariant()` and `matchVariant()` APIs
1. Added tests for the hoisting logic, prelude resolution for `&`
selectors
1. Added browser based UI tests to make sure that the non-flattened
version and flattened version behave the same.


---

Sorry about all the sandwiches, I'm hungry myself.

---------

Co-authored-by: uditDewan <udit.dewan21@gmail.com>
2026-07-31 12:36:44 +02:00
Robin Malfait
f6b7c5818e
Update integration tests: PostCSS re-emits the UTF-8 BOM character (#20372)
This PR updates the integration tests that started failing because of a
recent PostCSS update.

PostCSS v8.5.24 started re-emitting the BOM character in the output,
see: https://github.com/postcss/postcss/releases/tag/8.5.24 therefore
the tests started failing.

This PR just re-adds that back.

It might not be clear from the diff on GitHub, but this is the change: 
<img width="878" height="84" alt="image"
src="https://github.com/user-attachments/assets/5e2fbb6e-2ee4-46c5-9a2c-9c3a1a806bb0"
/>


## Test plan

1. All tests pass again [ci-all]
2026-07-31 12:19:43 +02:00
Robin Malfait
6de87c62c4
Fix changes to symlinked files outside of the project works (#20356)
This PR fixes an issue where changes to a symlinked file wouldn't result
in hot-reload when using `@tailwincdss/vite`. This issue also exists in
the other packages such as `@tailwincdss/postcss`,
`@tailwincdss/webpack` and `@tailwincdss/cli`.

The issue is that we watch the symlinked file, but not the "real" file
for changes. If the source of the symlinked file lives in another folder
that is not covered by auto-source detection or by any of the `@source`
directives, then changes to that file won't trigger a change.

To solve this, if a file is symlinked or lives in a symlinked folder,
then we will make sure that the `scanner.files` contains the real path /
canonicalized path to the real file as well just so we can detect
changes in that file.

Fixes: #20346
Closes: #20347

## Test plan

1. Added a regression test for `@tailwindcss/vite` 
2. Added tests in the scanner code itself
3. Manually tested on the reproduction:

| &nbsp; | Initial state | After change |
| ---: | --- | --- |
| **Before** | <img width="3200" height="1800"
alt="file-f8616573eec3150ae484fd279204c6d7"
src="https://github.com/user-attachments/assets/2734ecc3-b5b6-420e-820d-8a4a8fcdd7f5"
/> | <img width="3200" height="1800"
alt="file-0e93fd3c2c9694c844b098616a3208d2"
src="https://github.com/user-attachments/assets/fd86859b-420b-476b-80f6-e94d02e8b07b"
/> |
| **After** | <img width="3200" height="1800"
alt="file-f8616573eec3150ae484fd279204c6d7"
src="https://github.com/user-attachments/assets/2734ecc3-b5b6-420e-820d-8a4a8fcdd7f5"
/> | <img width="3200" height="1800"
alt="file-eb7b37430ea1363dddeac7226007afe4"
src="https://github.com/user-attachments/assets/0a95b1a4-a357-491d-b57f-78460c0fc9db"
/> |


[ci-all]

---------

Co-authored-by: Nic <162764842+Nic-Polumeyv@users.noreply.github.com>
2026-07-28 12:40:02 +02:00
Robin Malfait
4c128662c2
Fix failing upgrade test in CI (#20357)
The last CI run on `main` was on July 16, and the last `globby` release
(16.2.2) was released on July 15 (~21 hours earlier). Due to the default
`minimumReleaseAge` in pnpm of 24 hours, it meant that we were receiving
the previous globby version.

In the new version, they slightly changed some of the `.gitignore`
parsing, but in one of our upgrade tests we wrote an invalid
`.gitignore` file (which had indented lines).

This PR fixes that by using the `txt` tagged template literal, which
behind the scenes uses `dedent` which in turn makes sure that we ship a
proper `.gitignore` file.


## Test plan

All tests should pass [ci-all]
2026-07-28 00:21:06 +02:00
Robin Malfait
094bf62605
update release date 2026-07-16 19:15:10 +02:00
Robin Malfait
c2b24dd15f
4.3.3 (#20334)
Some checks failed
Release / Build aarch64-apple-darwin (oxide) (release) Has been cancelled
Release / Build x86_64-apple-darwin (oxide) (release) Has been cancelled
Release / Build x86_64-unknown-freebsd (OXIDE) (release) Has been cancelled
Release / Build aarch64-unknown-linux-gnu (oxide) (release) Has been cancelled
Release / Build armv7-unknown-linux-gnueabihf (oxide) (release) Has been cancelled
Release / Build x86_64-unknown-linux-gnu (oxide) (release) Has been cancelled
Release / Build x86_64-unknown-linux-musl (oxide) (release) Has been cancelled
Release / Build aarch64-unknown-linux-musl (oxide) (release) Has been cancelled
Release / Build aarch64-linux-android (oxide) (release) Has been cancelled
Release / Build armv7-linux-androideabi (oxide) (release) Has been cancelled
Release / Build aarch64-pc-windows-msvc (oxide) (release) Has been cancelled
Release / Build x86_64-pc-windows-msvc (oxide) (release) Has been cancelled
Release / Build and publish Tailwind CSS (release) Has been cancelled
Release / notify (release) Has been cancelled
2026-07-16 07:21:50 -04:00
Robin Malfait
bdcd7087b3
Don't trigger a full page reload for scanned files that Vite processes as modules (#20336)
This PR fixes an issue where editing a scanned file that Vite (or one of
its plugins) can process as a module, but that isn't currently loaded,
caused `@tailwindcss/vite` to force a full page reload, throwing away
all client state.

The `hotUpdate` hook has a fallback that sends a `full-reload` for files
that Tailwind scans but that Vite knows nothing about (e.g. `.php` or
`.blade.php` templates rendered by a backend). Without it, edits to
those files wouldn't refresh the page at all. To detect those files we
check whether every module for the changed file is an `asset` and/or has
no id, because the scanner's `addWatchFile` calls create exactly such
placeholder nodes for every scanned file.

The problem is that a source file that Vite _can_ process, but that
isn't loaded yet, looks exactly the same. The realistic way to get into
that state is code splitting: with route-level splitting (e.g.
`React.lazy`, TanStack Router's `autoCodeSplitting`, lazy routes in
`vue-router`), every component behind an un-visited split boundary only
exists as a scan placeholder in the module graph. Editing any of them
reloaded the whole app. The same happens for component stylesheets that
a framework plugin compiles into the component (e.g. Angular via
Analog), which never show up as their own module.

A full reload is never useful for these files: if the file is loaded,
Vite's own HMR handles it, and if it isn't loaded, reloading the page
won't load it either. Any new candidates still apply through the regular
`css-update` flow because the file is registered via `addWatchFile`.

So instead, we now skip the fallback when the changed file is handled by
Vite's module pipeline:

- The file exists as a real module in another environment (e.g. an
SSR-only module). This check already existed and is folded into the same
code path.
- The file is part of the JS/TS or CSS families, which Vite transforms
natively.
- For any other file type (e.g. `.vue`, `.svelte`, or `.md` with an SSG
plugin), a file with the same extension exists as a real module in some
environment's module graph, then a plugin does handle this file type and
the changed file just isn't loaded (yet).

External templates like `.php` files still trigger a full reload exactly
like before.

Fixes: #20320
Fixes: #19903
Closes: #20323

## Test plan

1. Added integration tests to ensure extensions handled by default rely
on HMR
2. Added integration tests to make sure that unknown extensions that
have been handled already will also use HMR
3. Manually tested that changing a `.php` file still triggers a
`full-reload`
4. Manually tested the reproduction where local client state isn't
thrown away


<img width="594" height="100"
alt="file-14a86a90a1e4b810c2b80338ea688572"
src="https://github.com/user-attachments/assets/a4507502-4a5d-43ee-93b9-14c793a25891"
/>

<img width="1122" height="1376"
alt="file-a5121da2ad77b95fdd1703560ef1ff41"
src="https://github.com/user-attachments/assets/cc5c67b0-b5ad-481f-8823-a3266d75357d"
/>
2026-07-15 17:49:05 +02:00
Robin Malfait
7811d74f37
Ensure earlier @source is not accidentally ignored by later @source (#20335)
This PR fixes an issue where an `@source` pointing to a file in a nested
folder was not scanned when a later `@source` pointed to a file in a
parent folder.

E.g.:

```css
@source "./nested/index.html";
@source "./index.html";
```

When using `@source` pointing to a specific file, then we want to make
sure that we ignore _other_ files since they are not listed explicitly.
To ensure that these patterns don't read other files, we inject a `*`
ignore pattern before it. You can think of the above being expanded to:

```rs
Ignored { base: "/project/src/nested", pattern: "*" }
Pattern { base: "/project/src/nested", pattern: "/index.html" }
Ignored { base: "/project/src", pattern: "*" }
Pattern { base: "/project/src", pattern: "/index.html" }
```

The problem with this is that the `Ignored { base: "/project/src",
pattern: "*" }` pattern results in ignoring the `nested` folder as well.
This means that we never even walk into the `nested` folder, so the
earlier `@source "./nested/index.html"` never matches anything.

We could switch the order in user land, but that's going to be hard to
maintain (and order matters for undoing/redoing earlier rules, so we
can't re-order internally either). Instead, we can scope the ignore
pattern to the _current_ path only, and not deeply nested. In other
words, the pattern should become:

```diff
- *
+ /*
```

It's a very subtle difference, but the pattern from above will now
become:
```diff
  Ignored { base: "/project/src/nested", pattern: "*" }
  Pattern { base: "/project/src/nested", pattern: "/index.html" }
- Ignored { base: "/project/src", pattern: "*" }
+ Ignored { base: "/project/src", pattern: "/*" }
  Pattern { base: "/project/src", pattern: "/index.html" }
```

We already do this when an unrestricted root (e.g. `@source "./nested"`)
lives inside the base of a restricted pattern. This works because every
source base is also its own walk root, and a `/*` pattern only matches
direct children so it can't ignore anything when walking from the nested
root itself.

This PR extends that same check to restricted pattern bases: if another
`@source` pattern has its base nested inside the current base, we emit
`/*` instead of `*`.

Note that we only relax the pattern to `/*` when such a nested root
actually exists. Sibling folders that no `@source` points into (e.g. an
`ignore-me` folder next to `nested`) are still direct children, so they
still match `/*` and are never walked.

Fixes: #20333

## Test plan

1. Added a regression test with the reproduction setup
2. Added a similar test with another sibling folder that should still be
ignored
3. Tested it against the actual reproduction:

Before:
<img width="797" height="463" alt="image"
src="https://github.com/user-attachments/assets/893bb872-f5cf-4c5a-bfb5-2dc1043b5a39"
/>

After:
<img width="794" height="460" alt="image"
src="https://github.com/user-attachments/assets/a59aae45-68ad-4fe3-b21f-b568f535d24f"
/>

Notice that the `text-green-500` now appears as expected.
2026-07-15 13:35:00 +02:00
Robin Malfait
f861d5c5e6
use the same fix for template files
Forgot this as part of https://github.com/tailwindlabs/tailwindcss/pull/20329
2026-07-14 18:42:33 +02:00
Benoît Rouleau
a854b35bcf
Prevent @tailwindcss/upgrade from rewriting files inside node_modules when run from a workspace subpackage (#20329)
Fixes #20328.

## What changed

When `@tailwindcss/upgrade` is invoked from a subpackage of a workspace
(e.g. `pnpm --filter …` or `cd packages/foo && pnpm exec upgrade`), it
traverses `../../node_modules/…` and applies its v3 → v4 migration
transform (`@tailwind utilities;` → `@import 'tailwindcss/utilities'
layer(utilities);`) to files inside the installed `tailwindcss` package
itself — a self-referential import that later detonates as an infinite
CSS-resolution loop and reads like a "cache corruption" bug (matches the
report in #19726 / #20328).

Root cause: `globby`'s `isGitIgnored` only walks `.gitignore` files at
or below its `cwd`. When invoked from a subpackage, the workspace-root
`.gitignore` (which almost always lists `node_modules`) is never
consulted, and `../../node_modules/…` paths come back as **not**
ignored.

The fix anchors `isGitIgnored` at the git repository root instead of the
subpackage:

- New helper: `gitRoot(cwd)` in `src/utils/git.ts` — thin wrapper over
`git rev-parse --show-toplevel`.
- `analyze(stylesheets, { base })` in `src/codemods/css/analyze.ts` now
calls `isGitIgnored({ cwd: gitRoot(base) ?? base })`. Falls back to the
previous behavior when not inside a git repository or when `git` is
unavailable.

## Test

Added an integration test that reproduces the exact scenario: a pnpm
workspace with a subpackage that imports `tailwindcss/utilities.css`,
upgrade run from the subpackage, then asserts
`packages/css/node_modules/tailwindcss/utilities.css` still holds the
pristine `@tailwind utilities;` directive.

Verified the test fails on `main` and passes with this change.

The 415 existing upgrade unit tests still pass. Two npm-related snapshot
failures in `upgrade-errors.test.ts` (`half-upgraded v3 project to v4
(bun|npm)`) pre-exist this change — they're about newer npm's `npm
notice run` output that isn't in the snapshots, unrelated to what this
PR touches.


---

Edit by @RobinMalfait: going to run on all [ci-all] so we can make sure
it works on Windows as well.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-07-14 16:39:24 +00:00
Robin Malfait
e48c5e8047
Fix weird character rendering on Windows with Japanese locale (#20318)
This PR fixes an issue where some characters are incorrectly rendered on
Windows with the Japanese locale.

This is arguably a bug in the font that's loaded by Windows when it
encounters `system-ui`. But waiting for fixes there might ... take a
while.

Another option is to not change the defaults in Tailwind CSS and instead
let the users that support different locales implement a fallback by
overriding the `--font-sans` variable.

The biggest reason for me to _not_ change it in Tailwind CSS is that it
requires us to know what the (proper) fallback fonts need to be on a per
OS basis.

But the main reason why I did want to make the change is that MDN says
this about the `system-ui` font:

> Glyphs are taken from the default user interface font on a given
platform. Because typographic traditions vary widely across the world,
this generic is provided for typefaces that don't map cleanly into the
other generics.
>
> **Note:** As the name implies, `system-ui` is intended to make UI
elements look like native apps, and not for typesetting large paragraphs
of text. It may cause the displayed typeface to be undesirable for some
users—for example, the default Windows CJK font may render Latin scripts
poorly, and the `lang` attribute may not affect the displayed font. Some
operating systems do not allow customizing `system-ui`, while browsers
generally allow customizing the `sans-serif` font family. For large
paragraphs, use `sans-serif` or some other non-UI font family instead.
>
> —
https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-family#system-ui

There are PRs in other big projects that made this kind of change as
well. E.g.:

- https://github.com/withastro/starlight/pull/3729
- https://github.com/vuejs/vitepress/pull/4988

The reasoning for getting rid of `ui-sans-serif` is twofold:
1. Because the starlight PR seems very well tested, and they got rid of
it
2. In the event that the browser decided to load the broken font when it
encounters `ui-sans-serif`, then we will run into the same issue again.

Fixes: #19767
Fixes: #19768


## Test plan

1. `system-ui` is not used anymore, so the bug doesn't happen
3. Everything still looks the same for the places I checked, but it's
hard to know if this created _other_ issues on other OS + Locale
combinations...
2026-07-14 17:04:20 +02:00
Robin Malfait
b03e5e7731
Lazy load @parcel/watcher (#20325)
This PR ensure that we lazy load the `@parcel/watcher` in the
`@tailwindcss/cli`. This means that we don't have to load it at all when
using a normal build or when using `--watch --poll` combination.

The bigger reason is that on some platforms `@parcel/watcher` might not
work, and therefore the build will fail even if you don't use `--watch`
at all.

This PR fixes that by lazy loading it. Then, if we can't load it when
using `--watch`, a useful workaround is shown by using `--watch --poll`
instead.

This PR also improves showing errors such that the `error.cause`
property can be rendered as well.

Maybe in the future we can make use of deferred imports
(https://github.com/tc39/proposal-defer-import-eval)

Fixes: #20322

## Test plan

1. All tests still pass [ci-all]
2. Fabricated a fake error locally to prove that we can still use a
normal build and `--watch --poll` as a workaround

<img width="1122" height="1376"
alt="file-2f91d99cfb9eb0e24a196c2dd358c1f3"
src="https://github.com/user-attachments/assets/e37658ee-0827-422e-bd50-8ea5a85e1b7c"
/>
2026-07-13 15:53:42 +02:00
Robin Malfait
35a3e9c515
Always produce <length> value when optimizing --spacing(0) (#20319)
This PR fixes a type issue when using `--spacing(0)`. This construction
doesn't really make sense, but if you use it, it produces the value `0`
instead of `calc(var(--spacing) * 0)` which is fine if you use it in a
spot where a `<length>` data type can be used, then the `0` is
interpreted as a `<length>`.

E.g.: `padding: 0` and `padding: 0px` are equivalent.

However, if you use it in a CSS variable, then the `0` will turn in a
`<number>` if you don't have an `@property` definition for that CSS
variable.

This on its own isn't the issue, but if you later use it as part of a
`calc(…)` then the `<number>` instead of `<length>` type is being used.
As seen in #20315.

We could remove the optimization, and use `calc(var(--spacing) * 0)`
again, but this is a bit silly since it only makes your CSS file larger.

We could try to be smart, and only do it _if_ we're assigning to a CSS
variable (which #20317 is doing). But if your CSS variable _does_ use a
`<length>` then it's a non-issue. We would run into the same issue if
you use `width: calc(100% --spacing(0))`. This value is a bit silly
anyway, but it would originally resolve to `calc(100% +
calc(var(--spacing) * 0))`, the optimization of `calc(100% - 0)` would
make it invalid, so the fix in #20317 would not be enough.

Instead I opted for an inbetween solution, by always using `0px`. In
most cases we can use `0`, but in the places we can't the `0px` would at
least ensure that we are dealing with `<length>` data types.

This way we don't have to try to be smart to analyze where we use the
value, and `0px` is still better than the long `calc(var(--spacing) *
0)` value.

Fixes: #20315
Closes: #20317

## Test plan

1. Manually tested that now `--spacing(0)` does produce `0px` which has
a length type
2026-07-09 18:34:27 +02:00
Robin Malfait
4af47fbe94
Fix hues in achromatic theme colors to be none (#20314) 2026-07-09 13:36:34 +02:00
Robin Malfait
5835691d21
Handle CSS nesting natively (#20124)
This PR introduces a new feature where we will be handling the CSS
nesting ourselves.

We currently still rely on Lightning CSS in most places. But there are
situations where we don't use Lightning CSS out of the box:

1. During development, typically optimization/minification isn't setup
2. In places where it isn't as easy to run Lightning CSS such as in
`@tailwindcss/browser` or in Tailwind Play.

We handle CSS nesting in a single pass over the AST by tracking some
information as we go. It's not the most complex code, but there are some
tricky parts to make this happen in an efficient way, especially for the
few additional optimizations we handle.

While going over the AST, we will only emit CSS the moment we see
declarations or comments. This also means that this has a fun side
effect of removing CSS that ends up with empty nodes automatically.
(Caveat: there are exceptions for body-less rules such as `@layer foo;`
or `@charset "UTF-8";)

This also allowed us to do some cleanup in `optimizeAst` that tried to
do this as well, but now this will be handled by the code that handles
nesting automatically. Which is preferred because the version in
`optimizeAst` mutated the AST.

This also contains some optimizations where we merge adjacent at-rules
(with the same name / params), and adjacent rules with the same
selector, and get rid of declarations that are duplicated in a node.
(Caveat: there are exceptions, in case of `@font-family { … }` where we
don't want to merge them)

~~To ensure that this implementation is correct, I also added an oracle
implementation in the tests. This implementation does multiple passes
over the AST, because it does each step one by one, with minimal code.
Each step contains comments with examples to see what's happening in
that step. We then test the optimized version against this.~~ Once the
implementation was in place, and all the tests were passing, then I
deleted the oracle implementation. That way we don't have to keep it in
sync all the time.

While handling the nesting, we have to make sure that `&` exists and if
we replace it with a parent selector that we do use `:is(…)` semantics.
This means that:
```css
.foo {
  &:hover {
    color: red;
  }
}
```
Becomes:
```css
:is(.foo):hover {
  color: red;
}
```

We then also make sure that we optimize the selector by removing the
unnecessary `:is(…)` wrappers, but only if they were introduced by the
nesting logic. If _you_ wrote `:is(…)` in your CSS, we won't touch it.

If you look at the commits, the first thing we did is remove the
optimization step from Lightning CSS in the tests. Then we enabled our
CSS nesting handling code. This allows us to see the effect of the
changes we are making. At the end, we re-enabled Lightning CSS.

For now, this PR will be a step that happens before Lightning CSS is
executed, while still using Lightning CSS. But now this step will also
always happen in places where we don't use Lightning CSS at all.

This should not result in any breaking changes. It could result in
changed CSS output in environments where Lightning CSS isn't used. In
environments where it is being used, then there could be some
differences related to some selectors but they should result in the same
behavior with the same specificity.

While testing things, I noticed that there are some missed opportunities
for performance related to how we extract variables from declaration
values. I want to tackle `optimizeAst` in future PRs to make it simpler,
more performant, and maybe even merge it with the CSS nesting handling.

As part of testing this, I tested it against the tailwindcss.com
codebase which contains a lot of CSS (807.67 KB, 18 174 AST nodes)
because almost every utility is being used in examples.

The oracle implementation is rather slow:
```
[131.59ms]   ↳ oracle (step by step)
[129.23ms]     ↳ handleNesting(…)
[  2.32ms]     ↳ toCss(…)
```

But the final code is much faster (`<15ms`):
```
[ 10.11ms]   ↳ hand written (single pass)
[  8.40ms]     ↳ handleNesting(…)
[  1.67ms]     ↳ toCss(…)
```

In contrast, Lightning CSS takes: `[ 37.48ms] Optimized by Lightning
CSS`

One interesting thing to notice is that in big projects, this could add
`10ms` to the build, but Lightning CSS would then take less time to
process, which results in a no-op with better output.

One thing to keep in mind here is that Lightning CSS does more things,
such as normalizing values, handling vendor prefixes, CSS nesting, etc.

<details>

<summary>Some notes on how the algorithm works:</summary>

### The basic idea

When you have CSS that looks this:
```css
.foo {
  .bar {
    color: red;
  }
}
```

Then the AST looks like this:
```
[
  {
    kind: 'rule',
    selector: '.foo',
    nodes: [
      {
        kind: 'rule',
        selector: '.bar',
        nodes: [
          {
            kind: 'declaration',
            property: 'color',
            value: 'red',
            important: false
          }
        ]
      }
    ]
  }
]
```

When we walk this tree, and we encounter a `rule`, then we will track
the selector on a stack. When we are done walking over the rule, then we
will pop the selector from the stack. This means that the top-most
selector on the stack will always be the parent selector.
```ts
let selectorStack = []

walk(ast, {
  enter(node) {
    selectorStack.push(node.selector)
  },
  exit(node) {
    selectorStack.pop()
  },
})
```

The moment we encounter a `rule`, and if a previous rule was seen, then
we push the `selector` of the rule onto the stack, but in a way that the
`&` is already replaced by the selector. This way, a sibling rule will
also get the already-prepared parent selector.

The simple version looks like this:
```ts
walk(ast, {
  enter(node) {
    // In the real code we properly handle `&` replacement, and make sure that
    // parent selector is prepended if there is no `&` used in the selector of the
    // node.
    let selector =
      selectorStack.length > 0
        // At this point, we don't optimize anything related to the selector yet
        ? node.selector.replaceAll('&', `:is(${selectorStack.at(-1)})`)
        : node.selector

    selectorStack.push(selector)
  },
  exit(node) {
    selectorStack.pop()
  },
})
```

So far we aren't doing much yet, but the interesting part is when we
encounter a `declaration` (or a `comment`). The moment we see any of
those, then will we emit a node with the information from the
`selectorStack`.

We then also track the last node's `nodes` we created such that we can
push more declarations into it as a shortcut.

```ts
let result: AstNode[] = []
let nodes: AstNode[] | null = null
walk(ast, {
  enter(node) {
    if (node.kind === 'declaration') {
      // `nodes` is available, nothing special to do
      if (nodes) {
        nodes.push(node)
        return
      }

      // Track new nodes
      let nodes = [node]

      // Create a new node with a reference to `nodes` for future declarations
      let newNode = rule(selectorStack.at(-1), nodes)
      result.push(newNode)
    }
  },
})
```

The last important part is that whenever we see a new `rule`, then we
have to reset that `nodes` tracking variable such that we can create a
fresh node the next time we see a declaration.

For the `at-rules`, something similar happens but they are tracked in a
similar but separate stack. The idea there is that we can then wrap
those `at-rules` around the `newNode` we create. That way the at-rules
naturally float to the top.

I can keep going here, but I think if you're interested in this, then
you could go over the commits in this PR, or you can look at the
`ast.ts` implementation directly to see what's going on.

</details>

## Test plan

1. Existing tests should pass
2. New tests have been added to test the flattening of nested CSS
2026-07-07 18:28:41 +02:00
Lazizbek Ergashev
9b0e8af258
Ensure @tailwindcss/postcss rebuilds when the input CSS changes but its mtime is unchanged (#20310)
## Summary

`@tailwindcss/postcss` chooses between an incremental and a full rebuild
by comparing the mtimes of the entry file and its resolved
`@import`/`@config`/`@plugin` graph. It never looks at the input CSS
itself, so when that CSS is produced by an upstream tool (e.g. Sass) and
passed to the plugin via `process()`, it can change while the `from`
file's mtime stays the same. The plugin then re-emits its previously
cached output and silently drops the change.

This stores the input CSS per cache entry and takes the existing full
rebuild path when it differs from the previous compile, mirroring the
fix the CLI watcher already has for changed input files.

Fixes #20307

## Test plan

- Added a regression test in
`packages/@tailwindcss-postcss/src/index.test.ts` that compiles two
different inputs for the same on-disk `from` file (unchanged mtime) and
asserts the second compile reflects the new CSS.
- Confirmed it fails on `main` (the second compile returns the stale
first output) and passes with the fix.
- Ran the `@tailwindcss/postcss` package tests (all green) and checked
formatting with Prettier.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-07-06 11:32:20 +00:00
Robin Malfait
67c745efde
Fix bug in attribute selector parsing (#20303)
This PR fixes a bug in the selector parser where an attribute selector
followed by a type selector inside a compound selector resulted in the
wrong result.

Given you have this CSS:
```css
[data-foo]div {}
```

Then parsing it before this PR, would result in:
```ts
{
  kind: 'compound',
  nodes: [
    { kind: 'selector', value: '[data-foo]div' },
  ],
},
```

But with this PR, it's properly split:
```ts
{
  kind: 'compound',
  nodes: [
    { kind: 'selector', value: '[data-foo]' },
    { kind: 'selector', value: 'div' },
  ],
},
```

I also tweaked some of the comments in the parser that are unrelated,
but I was there already.

## Test plan

1. Added a regression test
2. All other tests should pass
2026-07-03 21:13:13 +02:00
한국
2683903b86
Support fractional opacity modifiers for named shadow sizes (#20302)
## Summary

`shadow-*`, `text-shadow-*`, `drop-shadow-*`, and `inset-shadow-*`
accept a bare (non-arbitrary) opacity modifier like `/50`, but the
named-size branch of all four utilities validates it with
`isPositiveInteger(candidate.modifier.value)` instead of
`isValidOpacityValue(candidate.modifier.value)` — the helper every other
opacity/alpha modifier in the codebase uses (`asColor`, used by `bg-*`,
`text-*`, `border-*`, `ring-*`, `fill-*`, `stroke-*`, `decoration-*`,
`accent-*`, `caret-*`, `outline-*`, `placeholder-*`, `divide-*`).

This means a fractional modifier like `/12.5` is silently ignored for a
*named size* (`shadow-sm/12.5` behaves exactly like `shadow-sm`,
dropping the modifier), while the exact same `/12.5` modifier works
correctly on the *color* variant of the same utility
(`shadow-red-500/12.5` → `color-mix(in oklab, var(--color-red-500)
12.5%, transparent)`), since that path already goes through `asColor`.

`drop-shadow-*` is worse: its named-size branch has an extra guard (`if
(candidate.modifier && !alpha) return`) that bails out of the *entire*
utility when a modifier is present but couldn't be resolved to an alpha
— so `drop-shadow-sm/12.5` produces no CSS at all.

This isn't a case of fractional percentages being unsupported by design
— the CHANGELOG entry that introduced `shadow-*/<alpha>` explicitly
describes it as controlling shadow **opacity**, and the color branch of
these same utilities already supports fractional values
(`shadow-red-500/2.25`, `/2.5`, `/2.75` are covered by existing tests).
The named-size branch just never got the same treatment.

**Repro** (verified with `pnpm --filter tailwindcss exec vitest run`):
- `shadow-red-500/12.5` → `color-mix(in oklab, var(--color-red-500)
12.5%, transparent)` (correct)
- `shadow-sm/12.5` → identical output to plain `shadow-sm` (modifier
silently dropped)
- `drop-shadow-sm/12.5` → **no CSS generated at all**
- `shadow-sm/50` (integer, control) → works correctly

## Fix

Replaced `isPositiveInteger(candidate.modifier.value)` with
`isValidOpacityValue(candidate.modifier.value)` in the four affected
utility definitions in `packages/tailwindcss/src/utilities.ts`
(`shadow`, `text-shadow`, `drop-shadow`, `inset-shadow`). No other
changes were needed — once `alpha` resolves correctly, `drop-shadow`'s
existing `if (candidate.modifier && !alpha) return` guard naturally
stops bailing out, since `alpha` is no longer `undefined` for valid
fractional modifiers.

## Test plan

- Added a regression test in
`packages/tailwindcss/src/utilities.test.ts` covering `shadow-sm/12.5`,
`text-shadow-sm/12.5`, `drop-shadow-sm/12.5`, and
`inset-shadow-sm/12.5`.
- Verified via `pnpm --filter tailwindcss exec vitest run` that this
test fails (modifier dropped / empty output) with the fix reverted, and
passes with it applied.
- Ran the full `tailwindcss` package test suite (`pnpm --filter
tailwindcss exec vitest run`) — 4685 tests passing, no regressions.
- Verified formatting on the changed files with `npx prettier --check`.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-07-03 12:34:02 +00:00
한국
04588b1e8f
Fix theme() in JS plugins returning unresolved object instead of DEFAULT value (#20299)
## Summary

When a CSS theme key defined via `@theme` (or a JS config's `theme`
object) shares a dash-separated prefix with a sibling key — e.g.
`--color-foo` and `--color-foo-bar` — calling `theme('colors.foo')` from
inside a JS plugin (`addUtilities`, `addComponents`, etc.) does not
resolve to the `foo` value. Instead it returns an internal
disambiguation object shaped like `{ DEFAULT: 'red', bar: 'blue',
__CSS_VALUES__: {...} }`, because there's no way to tell from CSS custom
property names alone whether `foo-bar` is a sibling key or a nested
sub-key of `foo`.

This same ambiguity was already fixed for the CSS-embedded `theme()`
function in #19097 (which unwraps to the `DEFAULT` key when present),
and the changelog entry for that PR states it fixes this "in JS configs
**and plugins**" — but the fix only touched `apply-compat-hooks.ts`'s
`resolveThemeValue`, not `createThemeFn`'s `theme` function that's
exposed directly to plugins in `plugin-functions.ts`. This PR closes
that gap by applying the same DEFAULT-unwrapping there.

Without this fix, passing the raw object into `addUtilities` (a very
natural thing to do, since a plugin author expects a string) produces
broken CSS — the reserved `DEFAULT` key gets mangled into a garbage
property name (`-d-e-f-a-u-l-t`) by the kebab-case conversion, and the
internal `__CSS_VALUES__` bookkeeping leaks into the generated
stylesheet.

### Minimal reproduction

```js
// tailwind.config.js (registered via @config, or any @plugin-registered plugin)
const plugin = require('tailwindcss/plugin')

module.exports = {
  plugins: [
    plugin(function ({ addUtilities, theme }) {
      addUtilities({
        '.example-foo': { color: theme('colors.foo') },
      })
    }),
  ],
}
```
```css
@import "tailwindcss";
@config "./tailwind.config.js";
@theme {
  --color-foo: red;
  --color-foo-bar: blue;
}
```

**Before:**
```css
.example-foo color {
  -d-e-f-a-u-l-t: red;
  bar: blue;
}
.example-foo color __CSS_VALUES__ {
  -d-e-f-a-u-l-t: 0;
  bar: 0;
}
```

**After:**
```css
.example-foo {
  color: red;
}
```

## Test plan

- Added a regression test in
`packages/tailwindcss/src/compat/plugin-api.test.ts` ("theme() resolves
the DEFAULT value when a bare CSS theme key shares a prefix with a
sibling key")
- Verified via `pnpm --filter tailwindcss exec vitest run` that this
test fails with the exact broken output shown above when the fix is
reverted, and passes once it's applied
- Ran the full `tailwindcss` package test suite (`pnpm --filter
tailwindcss exec vitest run`) — 4684 tests passing, no regressions
- Verified formatting on the changed files with `npx prettier --check`

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-07-02 13:20:14 +00:00
highoncomputers
b53fa096c9
fix: exclude iframes from focus-visible auto outline in Preflight (#20292)
## Description

Firefox's UA stylesheet already sets `iframe:focus-visible {
outline-style: none; }`, so Preflight's `:-moz-focusring { outline:
auto; }` rule overrides that and applies an unwanted auto outline to
focused iframes.

Adding `:where(:not(iframe))` to the selector preserves the improved
focus ring behavior for all other elements while respecting Firefox's
native iframe focus styling.

Fixes #19795

Edit by @RobinMalfait 

## Test plan

Before:
<img width="1054" height="383"
alt="file-f833142116d36e1e620290b97455f001"
src="https://github.com/user-attachments/assets/81aa170b-2cef-4964-934d-61f258335e1a"
/>


After:
<img width="1056" height="310"
alt="file-400d9214fedf5b43693e10580a4869de"
src="https://github.com/user-attachments/assets/8f8bb81d-6070-425d-8820-327bf9e88e79"
/>

---------

Co-authored-by: root <root@localhost.localdomain>
Co-authored-by: Kirk Loretz <kirk-loretz-fsn@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-07-02 10:39:28 +00:00
Robin Malfait
ef79119d4e
Bump dependencies (#20300)
This PR bumps most of the dependencies to the latest version. 

This also marked some dependencies using a range such that you get
updates for free during the installation:
```diff
 catalog:
-  enhanced-resolve: 5.21.6
+  enhanced-resolve: ^5.24.1

-  vite: 8.0.14
+  vite: ^8.1.2

-  webpack: 5.107.0
+  webpack: ^5.108.3
```

Fixes: #20291

## Test plan

1. All tests on all OSes still pass [ci-all]
2026-07-02 11:45:49 +02:00
Robin Malfait
e46b3d74ee
Canonicalization: make hex colors case insensitive (#20298)
This PR fixes an issue where hex-based colors in arbitrary properties
and values were considered case-sensitive even though they are
case-insensitive in CSS.

If you look at the linked issue, there is this input CSS:
```css
@theme {
  --color-brand-purple: #3f3cbb;
}
```

We expect that both `bg-[#3f3cbb]` and `bg-[#3F3CBB]` get canonicalized
to `color-brand-purple` but before this pr, only the first one would get
canonicalized that way (since it's a perfect match).

Technically a bunch more values are case-insensitive but a lot of them
_are_ sensitive so to get this 100% correct, a lot more parsing needs to
happen. I think we can start with this and expand the logic when needed.

Fixes: #20295

## Test plan

1. Added a regression test based on the linked issue
2. Added tests for arbitrary properties (`[color:#fff]` vs
`[color:#FFF]`), and tests for arbitrary properties (`bg-[#fff]` vs
`bg-[#FFF]`)
2026-07-02 00:04:44 +02:00
Robin Malfait
39656f7d9a
Add --poll option to @tailwindcss/cli (#20297)
This PR re-adds the `--poll` option to the `@tailwindcss/cli` that we
had in Tailwind CSS v3, but didn't in Tailwind CSS v4.

In Tailwind CSS v4, we started using `@parcel/watcher` instead of
chokidar for our watcher in the CLI. However, this currently doesn't
support a `--poll` option.

This PR implements our own `--poll` option such that you can use it in
environments where fs events don't work properly (e.g. Docker).

Polling can be enabled by using `--watch --poll`, in this case we will
poll every `250ms` (I'm open for a different default value). You can
also pick your own interval by using `--watch --poll 500` which is
defined in milliseconds.

The `--poll` option will be less efficient than a normal `--watch`. But
if you are in a situation where you can't use `--watch` on its own then
this is a good fallback.

One thing you can do today is run the build command manually. If you do
have some tooling that _does_ work on your machine (such as
[`watchexec`](https://github.com/watchexec/watchexec)) then you can
automatically perform a full build. The biggest downside of this
approach is that you are doing a full build every time, instead of an
incremental build.

With this PR, we try to fix that by still allowing incremental builds.
This should result in the same behavior as the normal `--watch`
function:

1. First run, will trigger a full build
1. When any of the source files changes:
- If all classes were already known, then it will be a no-op, but you
will see a log in the terminal about it.
- If a new class is detected, then the CSS will be updated, but it will
be much more efficient than a full rebuild
1. When the input CSS file changes, or any of its dependencies, then a
full rebuild will be triggered (such that your new `@utility` are
available, and `@theme` values are updated).

The implementation is a little bit more complex just because I didn't
want to spam the terminal output even if we are polling every `250ms`.

In the Oxide scanner we do track the modified times of each file. Every
`250ms` we traverse the file system and skip the files that we know
didn't change (since the mtime is the same). If the file was touched,
then we will parse it again to extract possible Tailwind CSS classes. We
will also track which files were scanned such that we can know whether
we have to trigger a full-rebuild or not (in case the input.css file or
any of its dependencies was changed).

Fixes: #18109
Fixes: #18540
Fixes: #15750

## Test plan

1. Existing tests pass
2. An integration test has been added for the `--poll` option
3. Tested it on the tailwindcss.com codebase:

<img width="679" height="320" alt="image"
src="https://github.com/user-attachments/assets/af858da5-3e07-448d-86bd-8eacdf3bf8d1"
/>

Annotated:
```
≈ tailwindcss v4.3.2

Done in 105ms                    Initial build
Done in 3ms                      Saved a file that resulted in a no-op
Done in 2ms                      Saved a file that resulted in a no-op
Done in 3ms                      Saved a file that resulted in a no-op
Done in 53ms                     Saved a file with a new class
Done in 2ms                      Saved a file that resulted in a no-op
Done in 2ms                      Saved a file that resulted in a no-op
Done in 3ms                      Saved a file that resulted in a no-op
Done in 88ms                     Saved a the input.css file
Done in 3ms                      Saved a file that resulted in a no-op
Polling for changes…
```
We check the file system every `250ms` by default, but we won't log to
prevent spamming the terminal.
2026-07-01 21:25:26 +02:00
Robin Malfait
056a155072
4.3.2 (#20281) 2026-06-29 10:10:00 -04:00
Robin Malfait
15b4a8c9af
Use version range for PostCSS (#20289)
This PR fixes an issue where new PostCSS release could lead to type
related issues if newer versions change the types.

Right now `@tailwindcss/postcss` uses a hardcoded PostCSS version. Let's
loosen this up and use a semver range instead.

Fixes: #20288

## Test plan

1. All tests still pass
2026-06-29 15:43:46 +02:00
Robin Malfait
c8b081d963
Add suggestions for named opacity modifiers (#20287)
This PR re-enables suggestions for named opacity modifiers that can be
configured via the `--opacity-*` namespace.

Before we launched v4, we got rid of this feature:
https://github.com/tailwindlabs/tailwindcss/pull/14278 &
https://github.com/tailwindlabs/tailwindcss/pull/14339
But later we re-added the feature, without updating intellisense
suggestions: https://github.com/tailwindlabs/tailwindcss/pull/15009

This PR re-adds suggestions for utilities that read from the
`--opacity-*` namespace for named modifiers. A lot of utilities use some
internal abstraction that get this for free once we use the
`colorUtility` setup, but some utilities don't use these, and I had to
add the `--opacity` modifier theme key manually.

Fixes:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1541

## Test plan

1. Update intellisense related tests now that we read from the
`--opacity-*` namespace
2. All tests pass

Before:
<img width="1122" height="1376"
alt="file-5462b830c5ebbd6046494713344ff5ff"
src="https://github.com/user-attachments/assets/ab0771c1-da8d-4179-b19d-183c2c567606"
/>


After:

<img width="1122" height="1376"
alt="file-9247e05165f72e8bb974673ee17bd35a"
src="https://github.com/user-attachments/assets/2afdd50f-6579-4a4c-bf9b-8d402fcf1184"
/>

Notice that the `/foo` suggestion is available
2026-06-29 15:35:45 +02:00
Robin Malfait
ba4c23eaff
update changelog 2026-06-26 13:32:56 +02:00
Robin Malfait
0c3554499e
Improve release (#20280)
This PR improves the release script:

- The `node-linker=hoisted` was missing for the `wasm32-wasi` package
(this is because of the pnpm v11 changes where it migrated the
noide-linker setup from `.npmrc` to a flag during `pack`)
- Notify discord in case the release fails

## Test plan

1. All tests should pass because we didn't change anything related to
code
2. Unfortunately, the only way to properly testing this is to merge it
2026-06-26 13:25:38 +02:00
Robin Malfait
94f7907b55
add margin of error to the test
Sometimes this is flaky on CI, even though it doesn't make any sense
because we use `setTimeout(_, 500)` which should _at least_ result in
500ms...

But sometimes this results into:
```
AssertionError: expected 499.76 to be greater than or equal to 500
```
2026-06-25 19:23:36 +02:00
Robin Malfait
38652a4638
migrate to pnpm v11 (#20273)
This PR bumps the repo to pnpm v11 (from v9). 

I kept running into weird Windows specific issues for the integration
tests due to some shims but they all pass right now.

Bumping to pnpm v11 also meant that everything is driven by a
`pnpm-workspace.yaml` file, and changes applied via the `pnpm` field in
the `package.json` don't work anymore.

This also moved the `node-linker` setup that was defined in the `.npmrc`
file for the wasm oxide build into the `pnpm-workspace.yaml` file.
Ideally this is scoped to just this package, but I couldn't get that to
work, so it's applied to all packages right now.

[ci-all]
2026-06-25 19:14:10 +02:00
Robin Malfait
7ff413bad4
Ignore unknown at-rule warnings for @position-try (#20277)
This PR ignores Lightning CSS warnings for the unknow at-rule
`@position-try`.

This is a temporary fix/workaround until support for `@position-try`
lands in Lightning CSS:
https://github.com/parcel-bundler/lightningcss/pull/1238

Fixes: #20275

## Test plan

Ran a test based on the issue:

Before:
```shellsession
❯ tw -i x.css -o out.css --optimize
≈ tailwindcss v4.3.1

Found 1 warning while optimizing generated CSS:

│   position-try-fallbacks: --flip-above;
│ }
│ @position-try --flip-above {
┆              ^-- Unknown at rule: @position-try
┆
│   top: auto;
│   bottom: 0;

Done in 29ms
```

After:
```shellsession
❯ tw -i x.css -o out.css --optimize
≈ tailwindcss v4.3.1

Done in 14ms
```
2026-06-25 13:21:12 +02:00
Robin Malfait
bb6a10937c
use .ts instead of .css
In newer Vite versions this extension matters, without the
vite/resolvers integration tests won't work.

Since this is still a placeholder fake file where Vite will run
`dirname` on, the actual file doesn't really matter as long as it's
relative to this file.
2026-06-25 13:03:10 +02:00
Robin Malfait
d5ca0aeac9
Handle template toolkit %]…[% syntax (#20269)
This PR handles template toolkit syntax as a pre-processor step such
that `%]` and `[%` are seen as valid boundary characters.

This is handled for the `.tt`, `.tt2` and `.tx` file extensions. It's
not handled if this syntax is used in `.html` files because then
everybody pays a pre processor cost even if you don't need this syntax
in most cases.

This now ensures that a `template.tx` like this:
```html
<div class="[% IF $is_open %]bg-white/40[% ELSE %]bg-white/10[% END %]"></div>
<!--                         ^^^^^^^^^^^          ^^^^^^^^^^^              -->
```
Extracts the classes in between those conditions correctly.

This also fixes a small issue related to Maud, a template engine for
Rust where conditionals like `p.text-black[condition]` caused the
`text-black` class not to be extracted. This is fixed as part of this PR
because it was commented on the linked issue.

Fixes: #20233 

## Test plan

1. Added a new extractor
2. Added regression tests
3. All existing tests pass
2026-06-22 15:40:58 +02:00
Robin Malfait
0fee7b55f9
Restrict walking sibling folders when using a pattern (#20263)
This PR fixes an issue where a `@source` pointing to a concrete file
could result in scanning the entire parent folder instead of only
looking for the file we are actually interested in.

When we optimize a `@source`, we move all the static parts of the
pattern to the `base`. This means that a `@source` like this:

```css
@source "../../app.config.ts";
```

Resolves to:

```rs
SourceEntry::Pattern { base: "/Users", pattern: "/app.config.ts" }
```

When walking the `base`, we would only emit an `!app.config.ts` rule.
This means that _everything_ in the `/Users` folder is still walked, and
the result is then thrown away. If you take a look at the `gitignore`
equivalent (which is what we build behind the scenes), then we would
essentially create the following:
```gitignore
!app.config.ts
```

But if you know how `gitignore` files work, then you know that this does
force `app.config.ts` to _not_ be ignored, but it doesn't say anything
about all the other files/folders.

The fix is to restrict the `base` so that we ignore everything in the
folder, and then explicitly re-include only the pattern we care about.
Fixing this would result in the following:
```gitignore
*
!foo.ts
```

In the reproduction from #20255 this takes the build from appearing to
hang (~22s) down to ~20ms on my machine.

There are a few edge cases we have to be careful about:

**Multiple patterns for the same base.** If we have multiple `@source`
directives for the same folder:

```css
@source "./src/foo.ts";
@source "./src/bar.ts";
```

Then blindly emitting `*` for each one would result in this `.gitignore`
equivalent:

```gitignore
*
!foo.ts
*
!bar.ts
```

Notice that the second `*` would end up ignoring `foo.ts` again. To
avoid this, we only emit the `*` rule once per `base`.

**Dynamic parts in intermediate folders.** If the pattern still contains
a `*` in one of its folders:

```css
@source "./src/ba*/*.html";
```

This resolves to:

```rs
SourceEntry::Pattern { base: "/src", pattern: "/ba*/*.html" }
```

If we now inject the `*` rule for `/src`, then we would never walk the
`ba*` folders (e.g. `bar` or `baz`). To fix this, we add inverse rules
for each parent segment of the pattern:

```gitignore
*                     ← ignore everything
!/ba*/                ← except for the `ba*/` folders, so we walk into them
!/ba*/*.html          ← then scan the `*.html` files in them
```

**Bases already covered by a broader source.** If the `base` is already
included (or nested) under an unrestricted source (an `Auto`/`External`
source, or a `Pattern` containing `**`), then we leave it alone.
Restricting it would incorrectly hide siblings that the broader source
is supposed to pick up. For example:

```css
@source "**/*";
@source "./src/components/button.html";
```

Here the `**/*` source should keep auto-detecting every file, so we must
_not_ restrict `src/components` just because there's a more specific
`@source` pointing to it.

Source order is preserved throughout, so later `@source not …` rules can
still override an earlier restricted source.

Fixes: #20255

## Test plan

1. Added unit tests realted to this `sources` logic
2. Added integration like tests for the scanner itself
3. All other tests should pass as-s
4. Should work on each OS [ci-all]
2026-06-19 22:39:57 +02:00
depfu[bot]
c33b85d012
Update all pnpm dependencies (2026-06-19) (#20262)
This is your weekly update of **all** pnpm dependencies. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?

✳️ postcss-selector-parser (7.1.1 → 7.1.4, patch) ·
[Repo](https://github.com/postcss/postcss-selector-parser) ·
[Changelog](https://github.com/postcss/postcss-selector-parser/blob/master/CHANGELOG.md)
·
[Release](https://github.com/postcss/postcss-selector-parser/releases/tag/7.1.4)
·
[Diff](cf6637ed6a...4a7e4e3685)

✳️ semver (7.8.1 → 7.8.4, patch) ·
[Repo](https://github.com/npm/node-semver) ·
[Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md) ·
[Release](https://github.com/npm/node-semver/releases/tag/v7.8.4) ·
[Diff](76416081a8...8640bd68f1)

✳️ turbo (2.9.14 → 2.9.18, patch) ·
[Repo](https://github.com/vercel/turborepo) ·
[Changelog](https://github.com/vercel/turborepo/blob/main/RELEASE.md) ·
[Release](https://github.com/vercel/turborepo/releases/tag/v2.9.18) ·
[Diff](fc62fe0d9c...3bdce3277d)




---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will only send you the next scheduled PR once
you merge or close this one.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu refresh</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-06-19 13:11:16 +02:00
Robin Malfait
c46f654fa0
Ensure --alpha(…) is seen as a color, and --spacing(…) is seen as a length (#20260)
This PR fixes an issue where intellisense recommends this
canonicalization:

```diff
- text-[calc(var(--spacing)*4)]
+ text-[--spacing(4)]
```

Which is correct, but the issue is that the result is different due to
ambiguity of the `text-*` utilities.

```css
.text-\[calc\(var\(--spacing\)\*4\)\] {
  font-size: calc(var(--spacing) * 4);
}
.text-\[--spacing\(4\)\] {
  color: calc(var(--spacing, 0.25rem) * 4);
}
```

Notice that we're using `color` all of a sudden? This is because that's
the default and we infer the data type based on the arbitrary value. The
`calc(…)` infers that the type is `length`, but we don't know what the
type of `--spacing(…)` is so we fallback to the default type which would
be `color`.

This PR makes sure that built in functions like `--alpha(…)` and
`--spacing(…)` are resolved as `color` and `length` respectively.

With this fix in place, this is the result:
```css
.text-\[calc\(var\(--spacing\)\*4\)\] {
  font-size: calc(var(--spacing) * 4);
}
.text-\[--spacing\(4\)\] {
  font-size: calc(var(--spacing, 0.25rem) * 4);
}
```


Fixes: #20256
Fixes: #20258

## Test plan

1. Added a regression test to make sure this doesn't happen anymore
2026-06-18 15:02:18 +02:00
Robin Malfait
34c2b04a35
Deleting files should not crash Vite HMR (#20259)
This PR fixes an issue where Vite crashes when a file is deleted.

This is because we don't cleanup files/folders in the scanner when they
are being deleted. This results in an issue where we would use Vite's
`addWatchFile` API with deleted files.

This PR fixes that by making sure that when we re-scan the filesystem
that we clear out files/folders (and recompute globs).

Note: `candidates` themselve are still append-only until a fresh
compiler instance is created since the TypeScript based compiler relies
on that behavior.

Fixes: #17532 
Closes: #20111

## Test plan

1. Added an integration test based on the issue
2. Tested it directly in the reproduction repo

Before deleting:
<img width="988" height="1376" alt="image"
src="https://github.com/user-attachments/assets/0f104ff3-0f88-415a-8311-1cf54ab75d21"
/>


Before:
<img width="1389" height="1376"
alt="file-1b446568bfad50e6a8105d7be33f15f0"
src="https://github.com/user-attachments/assets/d2fbf03e-ca22-4bc4-9bb4-f3de10b90b99"
/>
Which results in:
<img width="988" height="1376" alt="image"
src="https://github.com/user-attachments/assets/8320d61c-4210-4aa8-aa8d-98c5327e9604"
/>

After:
<img width="1389" height="1376"
alt="file-c36ab0265856da5bb7c7a1e77427b821"
src="https://github.com/user-attachments/assets/2ec4c4ea-aa79-4a51-900a-e247dcdb976c"
/>
Which results in:
<img width="988" height="1376" alt="image"
src="https://github.com/user-attachments/assets/8e8dec0d-40eb-4e54-b5fd-8d22abd55973"
/>


[ci-all]
2026-06-18 14:29:04 +02:00
Robin Malfait
5e9f66e428
Ensure @variant can be used in JS based APIs (#20252)
This PR doesn't fix any issues, but it does add an integration test (as
a regression test) to make sure that `@variant` with default variants
and custom variants can be used inside of JS based plugin APIs.

In Tailwind CSS v4.3.1 we introduced a PR that handles `@variant` in the
`addBase` Plugin API
(https://github.com/tailwindlabs/tailwindcss/pull/19480). This was a bit
of an older PR, but the tests made sense, so it was merged.

However, by introducing that PR, we introduced a bug that the `@variant`
was handled too early. If you added custom variants later _and_ used it
in the `addBase`, then you would get an error since the variant isn't
available (yet).

That issue was fixed by
https://github.com/tailwindlabs/tailwindcss/pull/20247

Now the question remains, why did we even have the original PR when it
already worked?

The use case we had was using `@variant` as part of the
`@tailwindcss/typography` plugin configuration for one of our templates.
I was indeed able to reproduce the issue where `@variant lg` was seen in
the output CSS file.

Turns out that this template was using `@tailwindcss/typography` +
`@variant` in the configuration, but it was also using Tailwind CSS
v4.1.15.

Upgrading to the latest version automagically fixed the issue we had.
This is also the behavior you can see in the integration test.

The correct behavior was introduced in an even older PR
https://github.com/tailwindlabs/tailwindcss/pull/19263

All that said, everything should work in the next release related to
`@variant` usages inside JS based APIs.


**Tiny improvement**

While debugging what's going on, I noticed that we looped over the AST
to get some nodes out and we did that twice. This PR also improves that
by re-using the same list of nodes instead of computing it twice. This
won't have a huge impact, but it happened while compiling every single
utility which is not ideal.

## Test plan

1. All tests should pass
2. I can't see `@variant` in the output CSS file

Input:
<img width="655" height="323" alt="image"
src="https://github.com/user-attachments/assets/20c8d524-3575-488c-b0c0-5c4669f37dc7"
/>

Before:
<img width="477" height="175" alt="image"
src="https://github.com/user-attachments/assets/045e2086-1d4b-487c-96ff-676351412935"
/>


After:
<img width="484" height="175" alt="image"
src="https://github.com/user-attachments/assets/24d950b1-7c28-40f8-96bc-81b38be0e7a3"
/>
2026-06-17 15:23:20 +02:00
Robin Malfait
707c23b955
Ensure custom variants can be used via @variant in addBase (#20247)
This PR fixes an issue where `@variant` inside `addBase` is being used
with a custom variant.

The issue is that we substitute the `@variant` calls immediately when we
call the `addBase` function. That means that variants that aren't
processed yet will error out.

This is a regression, because this used to work in Tailwind CSS v4.3.0
and started failing in Tailwind CSS v4.3.1.

## Test plan

1. Added a regression test
2026-06-16 23:05:50 +02:00
Robin Malfait
cc3b634fe0
Ensure @tailwindcss/cli watches the input file (#20246)
This PR fixes an issue where if the `--input` file (when using
`@tailwindcss/cli`) lives in an ignored folder, then changes to the
input file won't trigger a rebuild.

This happened in #17632 where the input file lives in the `assets/`
folder which is ignored by default.

However, if you make a change to the input file, then the CLI doesn't
restart which means that you can't update the `@source` directives
without quitting the program and re-starting it manually.

This PR solves that by automatically injecting an `@source` with the
path to the input file. This way the CLI will see changes, even if the
input file is in an ignored directory.

Fixes: #17632

## Test plan

1. Added an integration test
2. Existing tests pass
3. Reproduced it on the 


Before:
<img width="1122" height="1376"
alt="file-295ce4696b6b3199a3915c63f8bb50cd"
src="https://github.com/user-attachments/assets/09e5e965-38d1-474d-bfc6-3b5e9bca73b6"
/>

After:
<img width="1122" height="1376"
alt="file-301b2f2e06aee045957355014ae5de87"
src="https://github.com/user-attachments/assets/2b6d4cba-f456-4138-98bd-2259876fe70a"
/>
2026-06-16 18:38:29 +02:00
Robin Malfait
51f0760d29
Fix crash when using @tailwindcss/vite using deno v2.8.x (#20245)
This PR might fix an issue where resolvers don't work on deno v2.8.x
(they do work on deno v2.7.x) when using `@tailwindcss/vite`.

This happens when the `context.parentURL` is not actually a URL at all,
and therefore the `new URL(…)` just crashes. This PR will fallback to
the incoming result for files that are not passed proper URLs.

Fixes: #20232

## Test plan

1. Existing tests pass
2. Can't seem to figure out how to test this in the reproduction issue
without publishing first. So going to merge this and test the insiders
version instead.
2026-06-16 16:03:27 +02:00
Robin Malfait
127d17033a
Add bare value support for auto-rows-* and auto-cols-* (#20229)
This PR adds bare value support for `auto-rows-*` and `auto-cols-*`.

We first introduced `auto-rows-auto`, `auto-rows-min`, `auto-rows-max`
and `auto-rows-fr` (same for `auto-cols-*`) back in Tailwind CSS v1.9
(https://v1.tailwindcss.com/docs/grid-auto-rows#app) but we haven't
touched it since.

This PR now adds support for bare values that use the spacing scale.
That means that you can now use `auto-rows-12` or `auto-cols-12` which
will result in the following CSS:

```css
.auto-cols-12 {
  grid-auto-columns: calc(var(--spacing) * 12);
}
.auto-rows-12 {
  grid-auto-rows: calc(var(--spacing) * 12);
}
```

We could also add support for percentage based values. The only question
is what the syntax should be. This can either be `auto-rows-3/4` or
`auto-rows-75%`.

For the fraction case, we already have `w-3/4` and `aspect-3/4` even
though they both have a different value as a result:
```css
.aspect-3\/4 {
  aspect-ratio: 3/4;
}
.w-3\/4 {
  width: calc(3 / 4 * 100%);
}
```

We also have some precedence for the `%` value as well, e.g. `via-10%`
for gradient color stops.

I think I would personally towards the `auto-rows-75%` value instead of
`auto-rows-3/4`. The fraction syntax works great for aspect ratio
because that's literally what it is (`aspect-16/9`). The fraction also
works great for widths, because you typically have 2 elements next to
each other:
```html
<div>
  <div class="w-1/3"></div>
  <div class="w-2/3"></div>
</div>
```

Since you only use the `auto-rows-*` once on a parent element, I think
the `auto-rows-75%` makes a bit more sense than `auto-rows-3/4` since
there is no _other_ element (at least not that I can think of).


## TODO

- [ ] Support percentages?
   - [ ] Syntax A: `auto-rows-3/4`
   - [ ] Syntax B: `auto-rows-75%`
   - [ ] Syntax C: support both, but that seems silly

Percentage support isn't a blocker for this PR, since you can always use
`auto-rows-[75%]` if you want

## Test plan

1. Added a test to prove that this works
1. Existing tests still pass

Requested by:
https://github.com/tailwindlabs/tailwindcss/discussions/20225
2026-06-16 11:50:35 +02:00
Robin Malfait
abc4670a9a
Prevent crash when using @tailwindcss/cli using --watch on Windows (#20242)
This PR fixes an issue on Windows where the `@tailwindcss/cli` with the
`--watch` flag crashes when using a `@source` with a base path that
doesn't exist on disk.

This happens when setting up the `@parce/watcher` for directories that
don't exist. This PR essentially filters out these directories that
don't exist on disk to prevent the crash.

It might be that if you add the folder _later_, while the watcher is
already watching, that you have to restart the `@tailwindcss/cli` (or
save the `index.css` (the file that contains the `@source` directives),
this also recreates watchers from scratch).

If this issue causes problems for `@tailwindcss/postcss` and
`@tailwindcss/vite` in the future as well, then we can move this logic
back to Oxide. We do maintain the incoming `@source` files as best as
possible without resolving to absolute paths. Back when we did resolve
them, if that process error'd we just never returned the glob.

The root cause is still referencing folders that don't exist.

Fixes: #20231

## Test plan

1. Added a failing test, that did fail on Windows
2. Once fixed, all tests should pass

[ci-all]

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-06-15 17:20:31 +02:00
depfu[bot]
d1f9f53bf9
Update all pnpm dependencies (2026-06-15) (#20241)
This is your weekly update of **all** pnpm dependencies. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?

✳️ @emnapi/core (1.10.0 → 1.11.1, minor) ·
[Repo](https://github.com/toyobayashi/emnapi) ·
[Release](https://github.com/toyobayashi/emnapi/releases/tag/v1.11.1) ·
[Diff](ba84999164...6c3668c9af)

✳️ @emnapi/runtime (1.10.0 → 1.11.1, minor) ·
[Repo](https://github.com/toyobayashi/emnapi) ·
[Release](https://github.com/toyobayashi/emnapi/releases/tag/v1.11.1) ·
[Diff](ba84999164...6c3668c9af)

✳️ @tailwindcss/typography (0.5.19 → 0.5.20, minor) ·
[Repo](https://github.com/tailwindlabs/tailwindcss-typography) ·
[Changelog](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/CHANGELOG.md)
·
[Release](https://github.com/tailwindlabs/tailwindcss-typography/releases/tag/v0.5.20)
·
[Diff](e002ab89ad...e3714a3fe5)

✳️ @emnapi/wasi-threads (1.2.1 → 1.2.2, patch) ·
[Repo](https://github.com/toyobayashi/emnapi)




---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will only send you the next scheduled PR once
you merge or close this one.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu refresh</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
</dl></blockquote>
</details>

---------

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-06-15 13:13:32 +00:00
Robin Malfait
8a14a71010
4.3.1 (#20226) 2026-06-12 19:33:35 +02:00
Truffle
522288ca08
Serve ESM type declarations to ESM importers of @tailwindcss/postcss (#20228)
## Summary

Fixes #20219.

The `@tailwindcss/postcss` exports map serves the CJS-shaped
`dist/index.d.ts` (`export = _default`) for both the `import` and
`require` conditions, while the correct ESM declaration file
`dist/index.d.mts` (`export { _default as default }`) is built and
published but never referenced. Type checkers that treat the import
condition as ESM reject the default import with TS1192 — concretely,
`deno check` on Deno 2.8.3+ (which ships TypeScript 6.0) fails on
`import tailwindcss from "@tailwindcss/postcss"`.

This splits the export entry into per-condition `types` blocks so ESM
importers resolve `index.d.mts` and CJS consumers keep `index.d.ts` —
the same shape `@tailwindcss/vite` already uses (`"types":
"./dist/index.d.mts"`).

Two notes on the issue as filed:

- Stock `tsc` with NodeNext does **not** reproduce the error
(declaration file format follows the file extension and package `type`,
not the matched condition), so I didn't take the suggested `export =` →
`export default` change in `index.d.ts` — that would break CJS
consumers, and the correct ESM declarations already ship.
- `@tailwindcss/node` has the same single-`types` exports shape; happy
to follow up there if you want parity.

## Test plan

Against `@tailwindcss/postcss@4.3.0` with the published exports map,
then again with only this `package.json` change applied to the installed
package:

```sh
# Deno 2.8.3 (TypeScript 6.0), nodeModulesDir: auto
deno check main.ts   # before: TS1192 "Module ... index.d.ts has no default export" — after: passes
```

```sh
# typescript@6.0.0-dev (NodeNext), one ESM importer + one .cts require importer
tsc --noEmit         # passes both before and after (no regression for npm consumers)
```

`@arethetypeswrong/cli`:

| | published 4.3.0 | with this change |
|---|---|---|
| node16 (from ESM) | 🎭 Masquerading as CJS | 🟢 (ESM) |
| node16 (from CJS) | 🟢 (CJS) | 🟢 (CJS) |
| bundler | 🟢 | 🟢 |
2026-06-12 14:33:16 +00:00
Robin Malfait
12833aa4b3
Fix canonicalization bug where we end up with a high precision number (#20221)
This PR fixes a bug where we suggest a canonicalization with a high
precision number.

E.g.:

`w-[calc(100%/3.5)]` → `w-[28.571428571428573%]`

While this is technically correct, it's also not as user friendly. This
PR solves this issue by checking whether the result has a precision of
`.xx` at most. If that's not the case, then we keep the original
expression.

Fixes:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1591

## Test plan

1. Added an a regression test for this situation
2. Existing tests pass
2026-06-12 00:23:11 +02:00
Rohan Santhosh Kumar
97a5b3abfb
docs: fix double word 'to to' in test comment (#20216)
## Summary

Fixes a double word typo in a test comment: 'No need to to wrap' → 'No
need to wrap'.

## Test plan

This is a comment-only change in a test file. No functional changes.

Co-authored-by: Codex <codex@openai.com>
2026-06-11 12:07:00 +02:00
Robin Malfait
6b0eb5193e
Ensure @source globs ending in **/* preserve dynamic path segments to avoid scanning too many files (#20217)
This PR fixes an issue where we were over-scanning because we lost
pattern information when a `@source` ended in `**/*` and contained other
dynamic parts in the glob pattern.

Noticed this while debugging and working on #20214. Let's say you have
the following:
```css
@source './blog/*/foo/bar/baz/**/*';
```

We make sure that folders or patterns ending in `**/*` are converted to
"auto sources", meaning that auto content detection should be used in
these folders.

However, when doing so, we would only take the `base` path of the glob.
And since we have "dynamic" parts (the `*`) in the pattern, that should
not be the case.

So the `@source` from above, would be turned into:
```rs
SourceEntry::Auto { base = "/Users/projects/project/blog" }
```

Notice that we lose all the information related to `/*/foo/bar/baz/`.
While this would technically still work, it also means that we are
scanning **too many files and folders** because we're only interested in
folders in the `blog` folder that also contain `foo/bar/baz`.

If the `*` wasn't there, then this would be correct, because then we
would've moved the `/blog/foo/bar/baz` part to the `base` ahead of time,
and the pattern would just be `/**/*`. That would result in:
```rs
SourceEntry::Auto { base = "/Users/projects/project/blog/foo/bar/baz" }
```

This PR fixes that, by _not_ converting it to an auto-source, and
instead convert it to a pattern:
```rs
SourceEntry::Pattern {
  base: "/Users/projects/project/blog",
  pattern: "/*/foo/bar/baz/**/*"
}
```

Notice that the static part `blog` is still moved to the base path. But
the rest stays in the `pattern` part as expected.

## Test plan

1. Added a failing test to make sure this doesn't happen anymore
2. Existing tests pass 
3. Tested this on the tailwindcss.com codebase using `@source
"../blog/tailwindcss*/**/*";`

```diff
 diff --git a/./tailwindcss-55526.log b/./tailwindcss-55527.log
index ce79c5da..03e97598 100644
--- a/./tailwindcss-55526.log
+++ b/./tailwindcss-55527.log
@@ -2,50 +2,9 @@ INFO tailwindcss_oxide::scanner: Provided sources:
 INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/app", pattern: "../blog/tailwindcss*/**/*", negated: false }
 INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/.bun/bin", pattern: "bun", negated: true }
 INFO tailwindcss_oxide::scanner: Optimized sources:
-INFO tailwindcss_oxide::scanner: Source: Auto { base: "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog" }
+INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog", pattern: "/tailwindcss*/**/*" }
 INFO tailwindcss_oxide::scanner: Source: Ignored { base: "/Users/robin/.bun/bin", pattern: "/bun" }
 INFO discover_sources: tailwindcss_oxide::scanner: enter
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2022-05-23-headless-ui-v1-6-tailwind-ui-team-management/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2022-06-23-tailwind-templates-and-all-access/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2022-08-17-tailwind-framer-motion-template-and-tailwind-jobs/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2022-09-09-new-personal-website-heroicons-2-headless-ui-v17/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2022-12-15-protocol-api-documentation-template/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2023-04-24-new-changelog-template-and-the-biggest-tailwind-ui-update-ever/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2023-07-18-tailwind-connect-2023-recap/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2023-08-07-meet-studio-our-new-agency-template/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2024-05-24-catalyst-application-layouts/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2024-05-30-prettier-plugin-collapse-whitespace/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2024-06-21-headless-ui-v2-1/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2024-09-12-radiant-a-beautiful-new-marketing-site-template/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/2025-05-14-compass-course-starter-kit/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/automatic-class-sorting-with-prettier/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/building-react-and-vue-support-for-tailwind-ui/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/building-the-tailwind-blog/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/designing-tailwind-ui-ecommerce/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/from-900-to-1-how-we-hired-robin-malfait/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/headless-ui-unstyled-accessible-ui-components/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/headless-ui-v1-4/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/headless-ui-v1-5/demo.tsx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/headless-ui-v1-5/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/headless-ui-v1/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/headless-ui-v2/examples/HeadlessUIV2Examples.tsx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/headless-ui-v2/examples/StateAttributesExample.tsx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/headless-ui-v2/examples/anchor-positioning.tsx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/headless-ui-v2/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/heroicons-micro/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/heroicons-v1/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/hiring-a-design-engineer-and-staff-engineer/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/introducing-catalyst/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/introducing-heroicons/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/introducing-linting-for-tailwindcss-intellisense/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/introducing-tailwind-play/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/just-in-time-the-next-generation-of-tailwind-css/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/multi-line-truncation-with-tailwindcss-line-clamp/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/simon-vrachliotis-joins-tailwind-labs/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/standalone-cli/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/tailwind-plus/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/tailwind-ui-ecommerce/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/tailwind-ui-now-with-react-and-vue-support/index.mdx"
 INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/tailwindcss-1-5/index.mdx"
 INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/tailwindcss-1-6/index.mdx"
 INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/tailwindcss-1-7/index.mdx"
@@ -68,10 +27,4 @@ INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/t
 INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/tailwindcss-v4-beta/index.mdx"
 INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/tailwindcss-v4/color-palette.tsx"
 INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/tailwindcss-v4/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/utility-friendly-transitions-with-tailwindui-react/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/vanilla-js-support-for-tailwind-plus/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/welcoming-brad-cornes-to-the-tailwind-team/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/welcoming-david-luhr-to-tailwind-labs/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/welcoming-james-mcdonald-to-tailwind-labs/index.mdx"
-INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/tailwindlabs/tailwindcss.com/src/blog/whats-new-in-tailwindcss-on-youtube/index.mdx"
 INFO discover_sources: tailwindcss_oxide::scanner: exit
```

Notice now that a lot of files are skipped as expected since we-re not
accidentally over-scanning now.

[ci-all]
2026-06-11 11:59:58 +02:00
Robin Malfait
1bf4291e85
Fix @source with folders that are ignored (#20214)
This PR fixes an issue where a `@source` that's pointing to a folder
that is git ignored, is also ignored by the `@source` even if it's
explicitly added.

Internally, we convert `@source` directives from `PublicSourceEntry`s to
`SourceEntry`s where we have dedicated enum branches for `Auto`,
`Pattern`, `Ignored` and `External`.

The `Auto` one accepts a `base` path, and will be used for auto content
detection. However, these paths will make use of all the default auto
content detection rules, which includes git ignore rules.
We also have `External` where we link to something that's "external" to
the current repo. We can probably improve this name, but it's external
in the sense that it won't show up on GitHub for example, aka ignored.
We have some content dirs that we ignore by default, such as the
`node_modules` folder. When you do use `@source` with `node_modules` in
the path, then we will mark it as an `external` resource which does not
look at the `gitignore` related rules and allowing it to be included
this way.

The idea with this is that, even though the folder is ignored by
default, you can still include files from the folder by explicitly using
the `@source` directive.

The issue as seen in #19844 is using `vendor/` instead of
`node_modules/` which is _not_ ignored by default. While we can add
`vendor/` to this same ignored dirs list, it will result in a breaking
change because this folder is often used by the Laravel community to
store some resources in.

This PR fixes this problem by not only looking at the content dirs we
ignore by default, but also looking at the actual git ignore state of
this folder. If it turns out that this is ignored, then we promote the
`Auto` source to an `External` source.

Fixes: #19844
Closes: #20057


## Test plan

1. Added integration tests for this situation
2. Ran the fix on the reproduction from #19844. If we run the CLI with
the `DEBUG=*` environment variable, the log file produces these results:

```diff
diff --git a/./tailwindcss-29207.log b/./tailwindcss-30381.log
index bc3017c..921af0e 100644
--- a/./tailwindcss-29207.log
+++ b/./tailwindcss-30381.log
@@ -6,8 +6,9 @@ INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin
 INFO tailwindcss_oxide::scanner: Optimized sources:
 INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/GrimLink/tailwind-gitignore-bug/app/design/frontend/theme", pattern: "/**/*.phtml" }
 INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/GrimLink/tailwind-gitignore-bug/app/design/frontend/theme", pattern: "/**/*.xml" }
-INFO tailwindcss_oxide::scanner: Source: Auto { base: "/Users/robin/github.com/GrimLink/tailwind-gitignore-bug/vendor/acme/theme" }
+INFO tailwindcss_oxide::scanner: Source: External { base: "/Users/robin/github.com/GrimLink/tailwind-gitignore-bug/vendor/acme/theme" }
 INFO tailwindcss_oxide::scanner: Source: Ignored { base: "/Users/robin/.fnm/node-versions/v26.1.0/installation/bin", pattern: "/node" }
 INFO discover_sources: tailwindcss_oxide::scanner: enter
-INFO discover_sources: tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/GrimLink/tailwind-gitignore-bug/app/design/frontend/theme/index.phtml"
+INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/GrimLink/tailwind-gitignore-bug/app/design/frontend/theme/index.phtml"
+INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/GrimLink/tailwind-gitignore-bug/vendor/acme/theme/module/templates/component.phtml"
 INFO discover_sources: tailwindcss_oxide::scanner: exit
```

We're checking some `.gitignore` related files, so let's check on each
OS [ci-all]
2026-06-10 18:44:14 +02:00
depfu[bot]
1d5e15e9d8
Update all of react 19.2.6 → 19.2.7 (patch) (#20206)
<!--depfu-start-->
> 👉 **This PR is queued up to get rebased by Depfu**
<!--depfu-end-->





Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ react (19.2.6 → 19.2.7) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.2.7">19.2.7</a></h4>

<blockquote><h2 dir="auto">React Server Components</h2>
<ul dir="auto">
<li>Fixed missing <code class="notranslate">FormData</code> entries in
Server Actions which regressed in 19.2.6<br>
(<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/36566">#36566</a>
by <a
href="https://bounce.depfu.com/github.com/unstubbable">@unstubbable</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="eaf3e95ca9...6117d7cca4">See
the full diff on Github</a>. The new version differs by 5 commits:</p>
<ul>
<li><a
href="6117d7cca4"><code>Version
19.2.7 (#36591)</code></a></li>
<li><a
href="53a07de1a7"><code>[19.2.x]
Unified release process (#36550)</code></a></li>
<li><a
href="02c444c3f0"><code>[19.2.x][FlightReply]
Don&#39;t drop FormData entries in `decodeReplyFromBusboy`
(#36566)</code></a></li>
<li><a
href="750848e565"><code>[19.2.x][ci]
Create artifact attestations for builds on backport branches
(#36559)</code></a></li>
<li><a
href="e1cfa7b6de"><code>[19.2.x]
Enable CI (#36552)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-06-09 10:44:01 +00:00
depfu[bot]
e7d64e7648
Update next 16.2.6 → 16.2.7 (patch) (#20207)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ next (16.2.6 → 16.2.7) ·
[Repo](https://github.com/vercel/next.js)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v16.2.7">16.2.7</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg data-component="Octicon"
class="octicon octicon-info mr-2" viewbox="0 0 16 16" version="1.1"
width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8
8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5
7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0
1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1
1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>Backport documentation fixes for v16.2 (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/93804">#93804</a>)</li>
<li>[backport] Patch <code class="notranslate">playwright-core</code> to
resolve <code class="notranslate">_finishedPromise</code> on <code
class="notranslate">requestFailed</code> (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/93920">#93920</a>)</li>
<li>[backport] Fix dev mode hydration failure when page is served from
HTTP cache (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/93492">#93492</a>)</li>
<li>[backport] Fix catch-all <code
class="notranslate">router.query</code> corruption with <code
class="notranslate">basePath</code> + <code
class="notranslate">rewrites</code> (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/93917">#93917</a>)</li>
<li>[backport] Encode non-ASCII characters in cache tags at construction
(<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/93918">#93918</a>)</li>
<li>[backport] Fix server action forwarding loop with middleware
rewrites (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/93919">#93919</a>)</li>
<li>[backport] Turbopack: switch from base40 to base38 hash encoding (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/93932">#93932</a>)</li>
<li>[ci] Disable hanging node 24 typescript tests on 16.2 backport
branch (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/94164">#94164</a>)</li>
<li>[backport] Fix "type: module" in project dir when using standalone
or adapters (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/94050">#94050</a>)</li>
<li>[backport] Propagate adapter preferred regions (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/94200">#94200</a>)</li>
<li>[16.2.x] Don't drop <code class="notranslate">FormData</code>
entries (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/94240">#94240</a>)</li>
<li>[backport] feat(turbopack): add LocalPathOrProjectPath PostCSS
config resolution (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/94284">#94284</a>)</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to <a
href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a>, <a
href="https://bounce.depfu.com/github.com/icyJoseph">@icyJoseph</a>, <a
href="https://bounce.depfu.com/github.com/unstubbable">@unstubbable</a>,
<a href="https://bounce.depfu.com/github.com/mischnic">@mischnic</a>, <a
href="https://bounce.depfu.com/github.com/bgw">@bgw</a>, <a
href="https://bounce.depfu.com/github.com/timneutkens">@timneutkens</a>,
and <a
href="https://bounce.depfu.com/github.com/lukesandberg">@lukesandberg</a>
for helping!</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="ee6e79b179...9bd3c26a73">See
the full diff on Github</a>. The new version differs by 13 commits:</p>
<ul>
<li><a
href="9bd3c26a73"><code>v16.2.7</code></a></li>
<li><a
href="c63224f3d8"><code>[backport]
feat(turbopack): add LocalPathOrProjectPath PostCSS config resolution
(#94284)</code></a></li>
<li><a
href="63115c7987"><code>[16.2.x]
Don&#39;t drop `FormData` entries (#94240)</code></a></li>
<li><a
href="aef22fdc82"><code>[backport]
Propagate adapter preferred regions (#94200)</code></a></li>
<li><a
href="f126e72271"><code>[backport]
Fix &quot;type: module&quot; in project dir when using standalone or
adapters (#94050)</code></a></li>
<li><a
href="bda3e2aabe"><code>[ci]
Disable hanging node 24 typescript tests on 16.2 backport branch
(#94164)</code></a></li>
<li><a
href="7e16e07c02"><code>[backport]
Turbopack: switch from base40 to base38 hash encoding
(#93932)</code></a></li>
<li><a
href="6139f4b885"><code>[backport]
Fix server action forwarding loop with middleware rewrites
(#93919)</code></a></li>
<li><a
href="c021d10fe9"><code>[backport]
Encode non-ASCII characters in cache tags at construction
(#93918)</code></a></li>
<li><a
href="9184ddb1ae"><code>[backport]
Fix catch-all `router.query` corruption with `basePath` + `rewrites`
(#93917)</code></a></li>
<li><a
href="3279f01607"><code>[backport]
Fix dev mode hydration failure when page is served from HTTP cache
(#93492)</code></a></li>
<li><a
href="ffc4f29682"><code>[backport]
Patch `playwright-core` to resolve `_finishedPromise` on `requestFailed`
(#93920)</code></a></li>
<li><a
href="bc1791dd7e"><code>Backport
documentation fixes for v16.2 (#93804)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-06-09 12:38:33 +02:00
Robin Malfait
d01e103cc4
Add missing inset keyword for inset-shadow-none (#20208)
This PR fixes an issue where the `inset-shadow-none` class did not use
the `inset` keyword. This PR fixes that by introducing the `inset`
keyword:

```diff
  .inset-shadow-none {
-   --tw-inset-shadow: 0 0 #0000;
+   --tw-inset-shadow: inset 0 0 #0000;
    box-shadow: var(--tw-inset-shadow), var
  }
```

While the end effect is the same (there will be no shadow), it also
means that we switch between the types of shadows. This results in the
fact that things like transitions don't behave the way they should.

A minimal reproduction looks like this:
https://play.tailwindcss.com/iOrnKWP49a (depending on when you visit
this link, it might be fixed)

## Test plan

1. Updated tests to reflect
2. Verified in the Vite playground that this is the case. In the video
you will notice that the transition is smooth in the fixed case, but
there is no transition in the current state:



https://github.com/user-attachments/assets/15d8c78e-6be5-4c6c-8df9-1044c14e8f39
2026-06-08 23:08:52 +02:00
Robin Malfait
68fcdf1f05
Do not crash when migrating empty classes (#20205)
This PR fixes a crash while using `npx @tailwindcss/upgrade` when
migrating classes with no body inside of an `@layer utilities`.

When running `npx @tailwindcss/upgrade`, one thing we do is migrate the
CSS from:
```css
@layer utilities {
  .foo {
    color: red;
  }
}
```
To:
```css
@utility foo {
  color: red;
}
```

We already have some logic that leaves non-classe (IDs, attribute
selectors, ...) alone. But if we are migrating a class that has no body,
then we will migrate that as well:
```css
@layer utilities {
  .empty {
  }
}
```
Is turned into:
```css
@utility empty {
}
```

But later in the migration process this will result in an error because
a `@utility` has to have at least _some_ nodes.

Ideally, you don't even have CSS that has empty rules since it doesn't
have any effect in the browser (except of making your CSS file bigger),
but it could be that you don't have control over this file, so a fix is
still valid.

This PR solves that by leaving those rules alone, and keep them in an
`@layer utilities`.

Fixes: #20204

## Test plan

1. Added a dedicated test for this usecase
2026-06-07 21:50:35 +02:00
Robin Malfait
3f58e52e36
Ensure @source globs with symlinks are preserved (#20203)
This PR fixes an issue when working with `@source` and `@source not`
that involves symlinks.

Internally our sources are mapped to a source entry where we have a
`base` path and a `pattern`. You can think about this where we insert a
`.gitignore` file in the `base` path for the given pattern.

However, we optimize these entries to move as many "static" parts into
the base path. For example:
```css
@source "./some/folder/here/*.html";
```

Is mapped to something like:
```ts
{ base: "/projects/my-project", pattern: "./some/folder/here/*.html" }
```
We then optimize it by turning it into:
```ts
{ base: "/projects/my-project/some/folder/here", pattern: "*.html" }
```

While doing this, we also use `dunce::canonicalize` to resolve the
actual paths on disk. This means that a symlink is resolved to their
real paths. This can cause issues because the "real" path is not what
you wrote in the `@source` directives.

So before, it could be that you have this:
```css
@source "./some/symlinked-folder/here/*.html";
```
Which was mapped to this on the Rust side:
```ts
{ base: "/projects/my-project", pattern: "./some/symlinked-folder/here/*.html" }
```
But was then optimized to:
```ts
{ base: "/projects/my-project/some/actual-folder/here", pattern: "*.html" }
```

...and we lost the `symlinked-folder` information. This causes issues as
seen in #17985.

With this PR, we keep the symlinked information in those globs since
that's what you wrote in those `@source` directives.

While setting up integration tests, I stumbled upon an issue because I
wanted to test that ignoring a symlinked folder, but including a single
particular file of that ignored folder resulted in that file being
ignored as well. Let's look at an example:

```css
@source     '../lib';
@source not '../lib/ignored';
@source     '../lib/ignored/except.html';
```

Earlier I mentioned that we create `.gitignore` files based on these
`@source` directives. In this case, when we're dealing with a folder, we
use `**/*` as the contents.

Looking at the example above, we should essentially have something like
this:
```gitignore
# lib/.gitignore
# @source '../lib'
!**/*

# lib/ignored/.gitignore
# @source '../lib/ignored'
**/*

# @source '../lib/ignored/except.html'
!except.html
```

Since it's a `.gitignore` file, we have to invert the globs. But the bug
I noticed is that in reality the result of those gitignores didn't look
like the above, it looked like:
```gitignore
# lib/.gitignore
# @source '../lib'
!**/*

# lib/ignored/.gitignore
# @source '../lib/ignored/except.html'
!except.html

# @source '../lib/ignored'
**/*
```

Notice how the `!except.html` and `**/*` are flipped. When dealing with
`.gitignore` files, the order is important.

This was caused because internally we kept a `BTreeMap` of `BTreeSet`s
where the map was the base path and a set of patterns. The patterns were
sorted because of the `BTreeSet`... which is not what we want.

Fixes: #17985
Closes: #20091

## Test plan

1. Added new tests in the scanner tests (on the Rust side)
2. Added integration tests with a symlink to another folder, outside of
the current folder
2. Added integration tests with a symlink to another folder, inside of
the current folder
2. Added integration tests to ensure that the order of `@source` files
with a folder + file is sorted correctly.
3. Since we're dealing with symlinks in these tests, let's test all OSes
[ci-all]
2026-06-07 20:44:22 +02:00
Jordan Pittman
ad6693906a
Allow @variant to be used inside addBase (#19480)
This seems like an unintentional omission

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-06-04 10:46:40 +00:00
Chris Hasiński
d42b34abbf
fix: handle invalid UTF-8 in Ruby and Vue preprocessors (#19588)
## Summary

This PR fixes a panic that occurs when the Ruby or Vue preprocessors
encounter files with invalid UTF-8 bytes.

**The issue:**
- `ruby.rs:37` and `vue.rs:18` used
`std::str::from_utf8(content).unwrap()`
- This panics when processing files containing invalid UTF-8 bytes

**Error message:**
```
thread panicked at crates/oxide/src/extractor/pre_processors/ruby.rs:37:59:
called `Result::unwrap()` on an `Err` value: Utf8Error { valid_up_to: 45, error_len: Some(1) }
```

**The fix:**
- Wrap UTF-8 conversion in `if let Ok(...)` to gracefully handle invalid
UTF-8
- Skip regex-based template extraction when UTF-8 conversion fails
- Allow byte-level processing to continue (in Ruby's case)

This can happen in Rails projects when:
- Binary files are inadvertently scanned
- Files contain non-UTF-8 encodings  
- Files are truncated at multi-byte character boundaries during parallel
processing

## Test plan

- [x] Added `test_invalid_utf8_does_not_panic` test for Ruby
preprocessor
- [x] Added `test_valid_utf8_with_multibyte_chars` test for Ruby
preprocessor
- [x] Added `test_invalid_utf8_does_not_panic` test for Vue preprocessor
- [x] All existing tests pass (`cargo test pre_processors` - 43 tests)

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-06-04 10:35:47 +00:00
Robin Malfait
0612ddc7dc
Add Twig pre-processor (#20198)
This PR fixes an issue where `addClass(opacity-50)` and
`removeClass(opacity-50)` in Twig templates isn't extracted properly.

This fixes that by adding a pre-processor for `.twig` files. This is a
simple initial implementation where we drop the `(` and `)` from the
`addClass` and `removeClass` functions. We don't do any special handling
around escaped characters or parenthesis inside of strings. We will add
them when there is a use case for it. Until then, we'll keep it simple.

If the real API would've been `addClass('opacity-50')` then it would've
worked out of the box.

A workaround you can use today is by using spaces `addClass( opacity-50
)` that works as well.

Fixes: #19458
Closes: #20110

## Test plan

1. Added new tests for `twig` extraction
2. Added tests with nested `(` and `)` e.g. `addClass(p-(--value))`
which should properly extarct `p-(--value)`
2026-06-04 11:44:13 +02:00
depfu[bot]
0b58dd69fd
Update @napi-rs/cli 3.6.2 → 3.7.0 (minor) (#20197)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​napi-rs/cli (3.6.2 → 3.7.0) ·
[Repo](https://github.com/napi-rs/napi-rs)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-06-04 10:31:08 +02:00
Aarni Koskela
efae52c3af
Simplify CSS when using utilities that use a *-0 or *-1 value (#20196)
## Why?


While inspecting a Tailwind-powered site, I noticed selectors like

```css
.inset-0{inset:calc(var(--spacing) * 0)}
.inset-x-0{inset-inline:calc(var(--spacing) * 0)}
.top-0{top:calc(var(--spacing) * 0)}
```

which seem a bit silly, not to mention more complex for the end-user
device parsing the CSS.

## Summary

This PR adjusts helpers to not generate `calc(... * 0)` expressions.

https://github.com/tailwindlabs/tailwindcss/pull/19095 does a similar
thing on CSS AST, but that doesn't run during the build proper.

## Test plan

Ran `pnpm build && pnpm test && pnpm test:integration`. (Some unrelated
tests failed on my machine, hopefully less in CI.)

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-06-03 16:44:09 +00:00
Robin Malfait
0c5f8bfd88
Ignore the standalone CLI (#20139)
This PR fixes an issue where if you use the standalone CLI, and you move
the standalone CLI into the current project, then we would scan that
standalone CLI as-if it contains Tailwind CSS classes. Since the CLI
contains actual Tailwind CSS classes, and is in fact readable text, this
binary would've been used as a source.

There are a few ways of fixing this, we could hardcode all the known
names, but that would result in an issue if you rename the CLI. We could
check whether it's a binary format and look for magic numbers at the
top. We could also check for a shebang at the top of the file and skip
it that way.

While some of these solutions might still be useful for the future. For
now I fixed it by essentially always ignoring `process.execPath`. That
way we never ever scan the actual executable regardless of whether you
renamed it or not.

Fixes: #20134

## Test plan

- Added an integration tests
- Works on every OS [ci-all]
2026-06-02 12:49:53 +02:00
Robin Malfait
44818a6cf6
Ensure @tailwindcss/cli recovers from a deleted transitive dependency (#20137)
This PR fixes an issue where the `@tailwindcss/cli` can get into a
non-recoverable state when any of the transitive dependencies break.

Tailwind CSS has 2 kinds of dependencies:

1. All your templates
2. All dependencies that contribute to your configuration such as the
`input.css`, any plugins, any `tailwind.config.js` files and so on.

When a template changes, we just have to scan for new Tailwind CSS
classes and emit a new CSS file. But when the `input.css` file, or any
of its dependencies changes, then we want to perform a full rebuild.

The idea is that your `@theme` might have changed, or new plugins have
been added, or old plugins have been removed.

If you have an `input.css` file:
```css
@import "tailwindcss";
@config "./tailwind.config.js";
```

That relies on a custom config: `tailwind.config.js`:
```js
const theme = require('./my-custom-theme.js');
module.exports = {
  theme
}
```

If that file relies on yet another file: `./my-custom-theme.js`, then
changes there should also trigger a full rebuild.

Since we're dealing with JavaScript here, we want to clear the require
cache and rebuild the dependency tree such that another change to any of
these files triggers a full fresh build.

However, if any of those (transitive) dependencies are deleted, then we
will end up in an invalid state. Creating a new compiler will result in
a build error. The compiler won't be able to figure out the entire
dependency tree, and we're stuck.

Once the user fixes the potentially missing dependency, the watchers
will not be watching any of those files because we created a fresh
compiler.

With this PR, we fix that by keeping track of old paths and using those
while we are still in an invalid state. The moment everything is fixed,
a fresh dependency tree is created and everything starts working again
without you having to restart the `@tailwindcss/cli` command.

Fixes: #20113
Closes: #20114
Closes: #20133

## Test plan

- Added an integration test that removes the transitive dependency.
Re-adding that file later will recover the CLI state.
2026-06-01 19:15:40 +02:00
Robin Malfait
6b43b6400a
Canonicalization: limit arbitrary to bare values conversion (#20130)
This PR improves the canonicalization process by limiting the bare
values to a certain amount.

Before this PR, whenever we have an arbitrary value, e.g. `left-[6px]`,
then we prefer to use a bare value instead e.g. `left-1.5`. In most
cases, this makes sense.

However, there are places where this doesn't really make sense
(https://x.com/kettanaito/status/2059987396050268589)

- `left-[99999px]` → `left-24999.75 `

The hard part is to figure out _why_ this feels wrong. The `.75` could
feel wrong, but in the `left-[6px]` → `left-1.5`, the `.5` makes sense.
If we reduce that big number to `left-[99996px]` → `left-24999`, then
there is no floating point but it still feels wrong.

One possibility I can think of is to analyze the incoming value and see
if we find certain patterns. All repeating numbers, fun numbers like
`1337`, common numbers most programmers know such as `720px`, `1280px`,
etc.

But instead of that, I think it's more reasonable to limit the bare
value such that the `px` based value doesn't exceed a big number. We can
improve the logic if there are other cases that don't really make sense.

The biggest value we have in our default theme is `--breakpoint-2xl:
96rem`, which is equivalent to `1536px`.

So I think any bare value that results in a value `<= 1536px` should
probably be fine.

In this case, `left-[99999px]` would stay as `left-[99999px]`, but
`left-[6px]` is still converted to `left-1.5`.

Note: this is only happening for arbitrary values being converted to
bare values _if_ they use the `--spacing` variable internally.

Values such as `z-[99999999999]` will still be converted to
`z-99999999999`, since the intent is still clear.

## Test plan

1. Added new tests for these limitations
2. Other existing tests still pass
2026-05-29 21:31:47 +02:00
Robin Malfait
d4f24c5f16
Fix invalid canonicalization where 0<unit> was migrated to 0 (#20127)
This PR fixes a bug in the canonicalization process when we simplify /
fold declarations that contain `0<unit>` values.

The reason we even try to fold these in the first place is to simplify
values such as `m-[0rem]` to `m-0`. The more values we can
fold/canonicalize, the better we can suggest replacements _if_ they are
the same.

One thing we know in CSS is that if you have a `<length>` type, and that
value is `0<unit>`, then we can safely change that to just `0`.
```css
width: 0rem;
width: 0;    /* `0` is a <length> */
```
However, if this was part of a `calc(…)` (or another CSS math function),
then this could make the calc expression invalid:
- `calc(1rem + 0px)` → `calc(1rem + 0)` — this goes from _valid_ to
_invalid_
At runtime the `1rem` can be converted to a `px` based valued, then
`16px + 0px` makes sense. Adding `0` without unit does not.
- `calc(1rem * 0px)` → `calc(1rem * 0)` — this goes from _invalid_ to
_valid_
At runtime the `1rem` can be converted to a `px` based value, but `16px
* 0px` would result in `0px^2` which doesn't make sense either.

We will still normalize values such as `-0.0rem` to just `0rem`, but not
`0` if we know it's unsafe to do so.

If we end up with top-level `calc(…)` expressions that can be folded,
then we will try to do that:
- `calc(0px * -1)` → `0`
- `calc(calc(0px * -1) + 1rem)` → `calc(0px + 1rem)`
Notice that the inner `calc(…)` was folded to `0px` not `0` because that
would make the `calc(0 + 1rem)` invalid.
Additionally, we could potentially fold the `calc(0px + 1rem)` to just
`1rem`, but we have to make sure that we don't introduce valid values
from invalid values `calc(0s + 1rem)` would be invalid, but folding it
to `1rem` would make it valid which is not good.

Fixes:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1579
2026-05-29 14:43:26 +02:00
Robin Malfait
829cdc930a
duration must be at least 500ms 2026-05-26 23:34:51 +02:00
Robin Malfait
2666194d5c
Improve internal instrumentation module (#20119)
This PR improves some of the internal instrumentation tooling we have.

While working on another branch, I updated the instrumentation tooling
to have a few different ways of measuring what's going on.

Until now, we had an `I.start(label)` and corresponding `I.end(label)`.
While this works, it also means that you have to make sure that you call
`I.end(label)` before every `return` to track things properly.

With this PR, I added a `I.span(label, () => /* some callback*/{})` API
that essentially does that in one go. It also handles promises and
resturns the value that was returned from the callback. This can be
useful in situations where you have a one-liner:
```ts
let css = I.span('toCss(…)', () => toCss(ast))
```

If your callback is longer, then you end up in a situation where you
have to indent your code, and if you want to stop measuring you have to
drop code in 2 places and re-indent:
```diff
- I.span('label', () => {
    …
- })
```

For this situation, I also added a `using _ = I.track(label)` API
instead. This can also be used in any block and automatically inserts
the `I.end(label)` on every exit point. This relies on the new `using`
keyword, but we already relied on that for the instrumentation module.

Last but not least, the constructor accepts a `shouldReport` which
defaults to the `env.DEBUG`. The reason for this change is so that it's
easier to report / not report during development instead of swapping out
an environment variable. Again, this is internal so there is no public
API change happening here.

## Test plan

All tests should still pass.
2026-05-26 23:20:39 +02:00
Robin Malfait
cdfae994ed
Remove unused test (#20117)
This PR cleans up some old stale test that has been skipped from the
beginning.

While this test wants to prove that migrating from Tailwind CSS v3 to
Tailwind CSS v4 can handle the `#{!important}` SCSS notation, it doesn't
prove that we can migrate an entire SCSS project. SCSS has much more
special syntax and we never supported that.

Let's get rid of this test that's doing nothing at the moment.
Especially since we don't want to migrate SCSS projects. Enabling this
might result in the false sense that we _do_ support SCSS migrations
which is not the case.

Closes: #20106
2026-05-26 19:45:00 +02:00
Robin Malfait
749c45ef26
Expose index and siblings on walk context (#20109)
This PR is a small improvement of the current `walk` implementation
where we will expose the `index` and the `siblings` on the current
context.

During a walk, we walk over objects that contain a `nodes: []` field.
The `ctx.parent` that already exists is a reference to the parent node,
but `ctx.siblings` is a reference to the `ctx.parent.nodes`.

The `ctx.index` is the index of the current node we are walking in the
`siblings` array. This way we can prevent the awkward
`ctx.parent?.nodes.indexOf(node)` which is a bit silly because we
already know the nodes we're walking and its index...

The `ctx.parent` can be `null`, but the `ctx.siblings` will never be
`null`, this can be seen in a situation like this:

```ts
let ast: AstNode = [nodeA, nodeB]

walk(ast, (node, ctx) => {
  if (node === nodeA) {
    ctx.parent === null; // Because there is no parent
    ctx.siblings === ast; // Because that's the current list we're looping over

    // Before this PR, we would have to do something like:
    let siblings = ctx.parent?.nodes ?? ast
  }
})
```

In the above example, the `ast` is a separately variable, but if this
was inlined, we would run into some issues:
```ts
walk([nodeA, nodeB], (node, ctx) => {
  if (node === nodeA) {
    ctx.parent === null; // Because there is no parent
    ctx.siblings === ast; // Because that's the current list we're looping over

    // At this point, there is no way to get to the `[nodeA, nodeB]` list
    // without moving it to a variable first.
  }
})
```

So, this PR doesn't change much, the additional information we track is
already known information that is now exposed to the caller of the
`walk` function.

In this PR we did update some usages and got rid of some awkward
`ctx.parent?.nodes ?? []` and `ctx.parent.nodes.indexOf(…)` usages.

## Test plan

- All tests still pass as expected
2026-05-24 22:32:05 +02:00
Jiahan Chen
982e920df8
Add Rspack optional peer dependency for @tailwindcss/webpack (#20027)
## Summary

This PR makes Rspack support explicit for `@tailwindcss/webpack` by
adding `@rspack/core` as an optional peer dependency alongside
`webpack`.

The loader already works through Rspack's webpack-compatible loader API,
as shown in this runnable example:
https://github.com/rstackjs/rstack-examples/tree/main/rspack/tailwindcss.
The README and package description are updated to document that usage.

## Test plan

No Rspack-specific tests were added. The loader implementation is
unchanged, and the existing webpack loader tests exercise the same
loader API path that Rspack uses, which should cover the relevant
behavior.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-05-23 11:14:48 +02:00
Robin Malfait
73983e1cf5
Fix 'Sourcemap is likely to be incorrect' warnings when using @tailwindcss/vite (#20103)
This PR fixes an issue where a bunch of warnings would be shown related
to sourcemaps.

This happens when we are dealing with CSS files that are _not_ Tailwind
CSS roots. In that case, in the `transform` step, we return the `src` of
that module as-is because we didn't modify anything. However, when
nothing changed, you have to return a `NullValue` such as `undefined`.

So this is a stupid little fix, but it should get rid of a bunch of
annoying warnings.

Fixes: #19930

## Test plan

- Added an integration test to mimic the problem
- Other tests still pass
- Tested it against the reproduction provided in #19930


Before:
<img width="1887" height="1763" alt="8oQNG5Lqr2B"
src="https://github.com/user-attachments/assets/2d8af456-4176-4f18-92a3-5327e395ac6b"
/>


After:
<img width="1885" height="1404" alt="8oQND5kWSb4"
src="https://github.com/user-attachments/assets/7dd98ea4-126b-45d4-9411-0afbacd2c797"
/>
2026-05-22 17:57:25 +02:00
Robin Malfait
e0a0c46c85
Cleanup lockfile / dependencies (#20102)
This PR reduces the installed dependencies by cleaning up the
`pnpm-lock.yaml` file.

This also pins `@parcel/watcher` such that the lockfile is generated
properly becauase of the patched dependencies.

This is a follow-up of #19499, but up to date with the latest state of
the repo.

## Test plan

- Lockfile is simpler. Most dependencies stayed the same, and were
published _months_ ago. There are a few cases where we have more recent
published dependencies. There are 7 dependencies that were published in
the last ~24 hours: `node-releases@2.0.46` (10 hours ago),
`electron-to-chromium@1.5.361` (12 hours ago), `semver@7.8.1` (20 hours
ago), `terser@5.48.0` (20 hours ago), `webpack-sources@3.5.0` (5 hours
ago), `vite@8.0.14` (yesterday). All of these but the `terser` version
used OIDC.
- Socket.dev didn't report any issues with the changed dependencies
- All tests still pass

---------

Co-authored-by: James Garbutt <43081j@users.noreply.github.com>
2026-05-22 16:57:46 +02:00
Jordan Brough
a480043a8a
Add --silent option to suppress output in @tailwindcss/cli (#20100)
Edited by: @RobinMalfait 

This PR adds a new `--silent` option to the `@tailwindcss/cli` to
suppress output (except for errors).

## Test plan

- Existing tests pass
- A new integration test for the `--silent` option was added

---

Original:

## Summary

Small change to add a `--quiet` option.

@adamwathan previously said this type of thing [sounded like a good
idea](https://github.com/tailwindlabs/tailwindcss/issues/8050#issuecomment-1100164351):

> [I] have made a note about the --silent option idea which I think
definitely has value 👍🏻

This is helpful to keep logs high signal in some scenarios. For example,
when I want AI to sift through my foreman logs, where "Done in X"
becomes the dominant log message over time when running tailwind
alongside other things.

## Test plan

I've added an integration test.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-05-22 13:03:32 +00:00
Robin Malfait
9f9b7c5c6d
Remove pnpm-lock.yaml from wasm32-wasi (#20101)
This PR removes the `pnpm-lock.yaml` file from one of the sub-packages.
This is unnecessary and causes confusion. Since this is a monorepo, we
can make use of the `pnpm-lock.yaml` file in the root of the project
instead.

Closes #20097
2026-05-22 14:33:17 +02:00
Robin Malfait
8dcdb66e8a
Bump dependencies (#20095)
This PR bumps some of our dependencies, common dependencies were moved
to pnpm's `catalog` feature.

Closes #20092
Closes #20085
Closes #20075
Closes #20066
Closes #20062


## Test plan

- All tests still pass
- Each dependency was published some time ago. Webpack has an even newer
version that was published <15min ago. Will update that one later.

[ci-all]
2026-05-21 17:58:55 +02:00
Robin Malfait
161569a541
Improve GitHub Actions (#20093)
This PR improves GitHub workflows by making sure that:

- Uesed actions are pinned
- We don't persist credentials
- Remove caches from release workflows
- Prevent template expansion when using `env.…` in `run` blocks
- Use the `gh release` instead of `softprops/action-gh-release`
2026-05-21 12:03:39 +02:00
Robin Malfait
d03edefd19
Fix parsing bug in SelectorParser (#20090)
While working on another feature, I noticed that a selector such as
`.foo::before` was parsed as:

```ts
[
  {
    kind: 'compound',
    nodes: [
      { kind: 'selector', value: '.foo' },
      { kind: 'selector', value: ':' },
      { kind: 'selector', value: '::before' },
    ],
  },
]
```

Instead of:
```ts
[
  {
    kind: 'compound',
    nodes: [
      { kind: 'selector', value: '.foo' },
      { kind: 'selector', value: '::before' },
    ],
  },
]
```

So far this hasn't been a real issue in practice, but it is in a
follow-up PR that I'm working on. To keep things separated, I wanted to
fix this behavior in a dedicated PR instead.

## Test plan

1. Added a test case for this situation
2. Other tests still pass
2026-05-20 15:54:07 +02:00
Robin Malfait
2c4726d3d5
Simplify selector combinators (#20089)
This PR is a follow-up of #20088 to further improve selectors, in
particular the `combinator`.

This PR explicitly types the `combinator` as:
```ts
type Combinator =
  | ' ' // Descendant combinator
  | '>' // Child combinator
  | '+' // Next-sibling combinator
  | '~' // Subsequent-sibling combinator
```

This allows us to explicitly test for this pattern in various places,
without us having to call `.trim()` first to know what the actual
combinator was.

In the selector parser itself, we already did a `.trim()` to know
whether we were dealing with a descendant combinator or not. With this
PR, we further ensure that there is no whitespace involved aroudn these
combinators.

This introduces a small problem because we need to be able to re-print a
selector's AST. So if we don't track whitespace, we have to re-introduce
it. But there are situations where we don't want it at all (during
canonicalization).

To solve this, we introduced a `minify = false` option in the
`SelectorParser.toCss`. If it's false (the default), then we introduce
whitespace, otherwise we remove all whitespace.

## Test plan

1. All existing tests pass
2. Manual cleanup/trimming of descendants is no longer necessary

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-20 15:30:55 +02:00
Robin Malfait
fc17df05f2
Add list, compound, and complex Selector nodes (#20088)
This PR introduces a few more nodes in the `SelectorParser`:

- A `list` node
- A `complex` node
- A `compound` node

These names are closer to the CSS Selector AST names
(https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Selectors/Selector_structure),
and are also used in other libraries.

The problem today is that there are situations where we parse a selector
like: `#a.b > .c, .d` as:
```ts
[
  { kind: 'selector', value: '#a' },
  { kind: 'selector', value: '.b' },
  { kind: 'combinator', value: ' > ' },
  { kind: 'selector', value: '.c' },
  { kind: 'separator', value: ', ' },
  { kind: 'selector', value: '.d' }
]
```

Which is a very simple structure, but this contains a flaw that is
annoying to deal with in practice: In order to determine that we are
dealing with multiple selectors, we have to loop through the nodes and
see if a separator occurs somewhere.

The other fun thing is that we already know the difference between
selectors, combinators and separators. So if we tweak this structure a
little bit during parsing, then we can answer the question from above in
a much simpler way:

With this PR, we will parse the selector as:
```ts
[
  {
    kind: 'list',
    nodes: [
      {
        kind: 'complex',
        nodes: [
          {
            kind: 'compound',
            nodes: [
              { kind: 'selector', value: '#a' },
              { kind: 'selector', value: '.b' }
            ]
          },
          { kind: 'combinator', value: '>' },
          { kind: 'selector', value: '.c' }
        ]
      },
      { kind: 'selector', value: '.d' }
    ]
  }
]
```

It definitely looks more complex, but now that we have a `list` node, we
already know that we are dealing with multiple selectors.

If you squint your eyes, in the inner part there is a `compound`
selector. This is essentially a node where each sub-node can be squished
together with no spaces whatsoever.

The `complex` selector is there just to group everything together. In
other tools, a complex selector is often represented as:
```ts
{
  kind: 'complex',
  combinator: '>',
  lhs: { … },
  rhs: { … },
}
```
While I want to have the concept of a `complex` node, I didn't go with
this syntax just because I want to keep the concept of `nodes` which
means that we don't need any special handling when using `walk` (which
loops over `.nodes` internally).

The reason this complex node exists is because otherwise you would end
up with this structure:
```ts
[
  {
    kind: 'list',
    nodes: [
      {
        kind: 'compound',
        nodes: [
          { kind: 'selector', value: '#a' },
          { kind: 'selector', value: '.b' }
        ]
      },
      { kind: 'combinator', value: '>' },
      { kind: 'selector', value: '.c' }
      { kind: 'selector', value: '.d' }
    ]
  }
]
```
But if you look at the `list` node now, it's not clear that we are
dealing with `2` selectors since there are 4 nodes. We could solve this
by re-introducing the separator node (`,`). The fact that the `list`
exists tells us that we're dealing with `n` selectors. But to know which
selectors we're dealing with, then we have to look for that `,` node
again, which introduces the original problem.

This is just an internal refactor to make future changes easier.

## Test plan

1. Everything still works as expected (all tests pass)
2. No public API breaking changes, this parser was never exposed
2026-05-20 15:04:05 +02:00
Robin Malfait
cea8c9721b
Improve parsing of shadow values (#20080)
This PR fixes an issue where a `calc(…)` value used in a shadow value
was incorrectly marked as the color of that shadow.

We have this feature where we can have colored shadows, this requires us
to replace the color in a value with a `var(--tw-shadow-color,
<original-value-here>)` such that we can swap out the color.
For this, we have to parse the value and figure out what the color part.

This PR uses the `ValueParser` to parse values, then figure out what the
color part is. The biggest reason for this is that we know what a
"function" is and what a normal "word" is, which allows us to do more
fine-grained checks on a per-type basis.
We tracked a few more functions that we _know_ produce color values, and
a few cases that we know produce length values so therefore can't be the
color.

Some examples:

- `calc(…)`, `min(…)`, `max(…)`, `clamp(…)`, `--spacing(…)` — these all
produce length-values.
- `color(…)`, `color-mix(…)`, `rgba?(…)`, `--alpha(…)` … — these all
produce color values.

Notice that we added `--spacing(…)` and `--alpha(…)` as well, these are
custom functions that Tailwind CSS provides, but we know what it will
eventually map to internally.

Last but not least, we also detect named colors and hex-based colors.

Fixes: #20065 
Closes: #20074

## Test plan

1. Added an integration test, before the fix the test would've looked
lik this:

  ```diff
    .drop-shadow-calc {
- --tw-drop-shadow-size: drop-shadow(0 0 calc(1 * var(--spacing))
var(--tw-drop-shadow-color, black));
+ --tw-drop-shadow-size: drop-shadow(0 0 var(--tw-drop-shadow-color,
calc(1 * var(--spacing))) black);
      --tw-drop-shadow: drop-shadow(var(--drop-shadow-calc));
filter: var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, )
var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, )
var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, );
    }
  ```

2. Added more tests related to the shadow replacement logic itself where
we try to infer the color (or the length-values for x, y, blur, spread)
3. All other existing tests pass
2026-05-19 13:47:50 +02:00
Robin Malfait
36417cbd12
Properly negate custom variants with @container when used with not-* (#20059)
This PR fixes an issue where a custom variant declared with a
`@container` wasn't properly negated when using it in combination with
the `not-*` variant.

Given this CSS:
```css
@custom-variant has-a {
  @container style(--a) {
    @slot;
  }
}
```

If you then used `not-has-a:flex`, then the following CSS was produced:
```css
.not-has-a\:flex {
  @container style(--a) not {
    display: flex;
  }
}
```
But we expect the `not` to be in the correct location:
```css
.not-has-a\:flex {
  @container not style(--a) {
    display: flex;
  }
}
```

The issue was that we did some string related checks, and we assumed
that the `query` part of the `@container` (`style(--a)`) had to start
with a `(` character.

To fix this, we now parse the value to an AST, and verify the AST shape
before manipulating it. This now checks whether the `query` part is a
function (both `(…)` and `style(…)` are considered functions).

Also added some additional tests that were already handled, these cases
look like:

- `@container {query}`
- `@container not {query}`
- `@container {name} not {query}`
- `@container {name} {query}`


Fixes: #20058

## Test plan

1. Added a failing test for the use case of the linked issue.
2. Added a few more additional tests to explicitly track some use cases
we handled already but didn't track via tests.
3. All other tests still pass as expected.
2026-05-15 00:08:10 +02:00
Jordan Pittman
460a008120
v4: Allow @apply to be used with CSS mixins (#19427)
The CSS Custom Functions and Mixins spec [is using `@apply` with dashed
idents](https://drafts.csswg.org/css-mixins-1/#apply-rule) for native
mixin support. We shouldn't attempt to treat these as utilities and try
to compile them since they'll fail.

There one intentional limitation with regards to mixins and our use of
`@apply`: We do not allow users to mix utilities and mixins in the same
at-rule. In other words, all of the following `@apply` rules are
invalid:

```css
.foo {
  /* Invalid because the rules contain both mixins and utilities */
  @apply --my-mixin underline;
  @apply --my-mixin() underline;
  @apply underline --my-mixin;
  @apply underline --my-mixin();
}
```

Aside: Lightning CSS does not yet support this syntax so the results of
a production build won't produce the correct code but we'll at least
handle these correctly in Tailwind CSS itself.

Fixes #19422

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-05-14 14:22:26 +00:00
Robin Malfait
0db226a222
Add Vue integration test with 1000 components (#20055)
This PR adds an integration test with Vue where we use a 1000 components
and where each component references a CSS file via `@reference`. Each
component has a unique class that uses `@apply`.

There are some discussions in
https://github.com/tailwindlabs/tailwindcss/discussions/16429 that
mention that this causes OOM issues. Right now I can't reproduce that,
and even with a 1000 components, it produces CSS in a reasonable time:
```
vite v7.3.3 building client environment for production...
✓ 2011 modules transformed.
dist/index.html                   0.23 kB │ gzip:  0.18 kB
dist/assets/index-DNVNFkYQ.css  106.65 kB │ gzip: 10.79 kB
dist/assets/index-B8v7EbAN.js   223.84 kB │ gzip: 49.81 kB
✓ built in 3.17s
```

I also started a Vite server and triggered file changes to see if the
memory would grow forever, which it didn't. After a 1000 changes,
everything still behaves smoothly:
<img width="1694" height="1856" alt="image"
src="https://github.com/user-attachments/assets/b16800ae-4dce-4f0d-9d97-25f4cab21c1c"
/>

Making changes manually to a single component, result in proper HMR
request that update the browser:


https://github.com/user-attachments/assets/5c79ffc6-2329-4341-9d25-82b000093e31

This test is here to make sure that it keeps working in the future.

---

If I remove all `@reference` references, and usages of `@apply`, then
the build time is indeed faster:
```
vite v7.3.3 building client environment for production...
✓ 2011 modules transformed.
dist/index.html                   0.23 kB │ gzip:  0.18 kB
dist/assets/index-CcxXccJ1.css  106.61 kB │ gzip: 10.76 kB
dist/assets/index-M92YFF0G.js   223.84 kB │ gzip: 49.81 kB
✓ built in 1.97s
```

So we go from `1.97s` → `3.17s`, which is a `1.2s` increase when you use
`@reference` with `@apply` in 1000 files for a fresh build.

I also saw some comments about the CSS growing whenever `@reference` was
used, but as you can see in the snippets above they are at a stable
size.

## Test plan

1. All tests still pass

[ci-all] For testing on Windows / macOS

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-14 15:38:51 +02:00
Robin Malfait
559f92bbf8
Harden workflow security (#20054)
This PR improves the workflows a bit more by:

1. Making sure that we always use `pnpm install` with a frozen lockfile
2. Cleanup permissions
3. By not caching `~/.cargo/bin/`

## Test plan

1. Every test should still pass

[ci-all]
2026-05-14 13:00:37 +02:00
depfu[bot]
6ee13978e6
Update all of react 19.2.5 → 19.2.6 (patch) (#20050)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ react (19.2.5 → 19.2.6) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.2.6">19.2.6</a></h4>

<blockquote><h2 dir="auto">React Server Components</h2>
<ul dir="auto">
<li>Type hardening and performance improvements<br>
(<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/36425">#36425</a>
by <a href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a>
and <a
href="https://bounce.depfu.com/github.com/unstubbable">@unstubbable</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="23f4f9f30d...eaf3e95ca9">See
the full diff on Github</a>. The new version differs by 2 commits:</p>
<ul>
<li><a
href="eaf3e95ca9"><code>Version
19.2.6</code></a></li>
<li><a
href="795203e75e"><code>[FlightReply]
Type hardening and performance improvements</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-05-14 12:41:48 +02:00
Serge Yudin
a2b89a7012
Fix canonicalize crash with plugin component values (#20052)
## Summary

Fixes #20051.

The collapse canonicalization pass speculatively checks compatible
functional utility roots. When one of those roots comes from a plugin
registered with `matchComponents`/`matchUtilities`, the speculative
candidate can call the plugin callback with an arbitrary value that is
not present in the configured `values` map. Plugins such as the Phoenix
Heroicons helper expect the mapped value shape and can throw while
canonicalize is only probing possible replacements.

This change skips speculative replacement utilities whose property
lookup throws, matching the best-effort behavior already used by utility
signature generation. The original candidates are preserved instead of
crashing canonicalization.

## Test plan

- `source ~/.nvm/nvm.sh && nvm use 22.14.0 && pnpm vitest run
packages/tailwindcss/src/canonicalize-candidates.test.ts -t "does not
crash when plugin matchComponents rejects speculative values during
collapse"`
- `source ~/.nvm/nvm.sh && nvm use 22.14.0 && pnpm vitest run
packages/tailwindcss/src/canonicalize-candidates.test.ts`
- `source ~/.nvm/nvm.sh && nvm use 22.14.0 && pnpm prettier --check
packages/tailwindcss/src/canonicalize-candidates.ts
packages/tailwindcss/src/canonicalize-candidates.test.ts`

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-05-14 09:59:19 +00:00
Robin Malfait
eefe64593a
Cleanup tests (#20053)
This PR does some cleanup work in the tests such that every test is
using the custom test helpers instead of creating the compiler itself
and using other test helpers such as `optimizeCss` or `pretty`.

For tests we have some helpers that introduce a small layer of
indirection. While you typically want to avoid indirection, this one
allows us to keep the tests the same whenever we make changes to the
internals.

It also allows us to run through the full core flow where we have to
setup a compiler, pass in the CSS, compile the candidates, optimize the
CSS and pretty print it for snapshot purposes.

Our tests often look like this when you are testing some candidates:
```ts
test('…', async () => {
  expect(await run(['flex', 'hover:flex'])).toMatchInlineSnapshot(`
    "
    .flex {
      display: flex;
    }

    @media (hover: hover) {
      .hover\\:flex:hover {
        display: flex;
      }
    }
    "
  `)
})
```

If you need to compile some CSS, we used the following:
```ts
test('…', async () => {
  expect(
    await compileCss(css`
      @tailwind utilities;
      .foo {
        @apply flex;
      }
    `),
  ).toMatchInlineSnapshot(`
    "
    .foo {
      display: flex;
    }
    "
  `)
})
```

With this PR, I normalized the tests by changing the signatures of those
tests slightly:
```ts
export async function run(
  // A list of candidates
  candidates: string[],

  // Optionally, custom CSS
  input = css`
    @tailwind utilities;
  `,

  // Optionally, custom compile options
  options: Parameters<typeof compile>[1] = {},
) {
  let { build } = await compile(input, options)
  return pretty(optimize(build(candidates)).code)
}

// Same as above, but without the candidates
export async function compileCss(css: string, options: Parameters<typeof compile>[1] = {}) {
  return run([], css, options)
}
```

They are very similar, but if we migrate _everything_ to `run`, then
there will be situations where you have to use:
```ts
test('…', async () => {
  expect(
    await run(
      [],
      css`
        @tailwind utilities;
        .foo {
          @apply flex;
        }
      `,
    ),
  ).toMatchInlineSnapshot(`
    "
    .foo {
      display: flex;
    }
    "
  `)
})
```

Which is a little bit confusing because what does `[]` even mean?

The next big change is that a lot of the tests were manually setting up
the compiler, passing in the CSS and compiler options, then building the
candidates, then manually optimizing the CSS and then manually pretty
printing the resulting CSS for snapshot purposes.

Other tests, didn't use all those steps and skipped the optimization
step for example. This means that not all tests were testing the
end-to-end core workflow.

This PR uses the tests helpers wherever we could. This now also means
that our tests look the same as much as possible.

The biggest goal of this was to get everything in a similar state.
Future PRs that add additional optimizations will now see those
optimizations reflected in the actual test output.

## Test plan

1. All tests still pass
- Some test _output_ has changed because the `optimizeCss` will now kick
in. But this reflects production better anyway.
2. No source code was changed
2026-05-14 11:43:34 +02:00
depfu[bot]
ece4824f0d
Update jiti 2.6.1 → 2.7.0 (minor) (#20044)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ jiti (2.6.1 → 2.7.0) · [Repo](https://github.com/unjs/jiti) ·
[Changelog](https://github.com/unjs/jiti/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/unjs/jiti/releases/tag/v2.7.0">2.7.0</a></h4>

<blockquote><p dir="auto"><a
href="https://bounce.depfu.com/github.com/unjs/jiti/compare/v2.6.1...v2.7.0">compare
changes</a></p>
<h3 dir="auto">🚀 Enhancements</h3>
<ul dir="auto">
<li>Add explicit resource management (<code
class="notranslate">using</code>/<code class="notranslate">await
using</code>) support (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/422">#422</a>)</li>
<li>Support opt-in <code class="notranslate">tsconfigPaths</code> (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/427">#427</a>)</li>
<li>Support virtual modules (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/428">#428</a>)</li>
<li>Add <code class="notranslate">jiti/static</code> subpath (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/430">#430</a>)</li>
</ul>
<h3 dir="auto">🔥 Performance</h3>
<ul dir="auto">
<li>
<strong>interopDefault:</strong> Add caching to reduce proxy overhead by
~2x (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/421">#421</a>)</li>
</ul>
<h3 dir="auto">🩹 Fixes</h3>
<ul dir="auto">
<li>
<strong>require:</strong> Passthrough resolve options (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/412">#412</a>)</li>
<li>
<strong>require:</strong> Fallback to transpilation when <code
class="notranslate">tryNative</code> fails (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/413">#413</a>)</li>
<li>Fallback for <code class="notranslate">ENAMETOOLONG</code> when
evaluating esm (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/429">#429</a>)</li>
</ul>
<h3 dir="auto">📦 Build</h3>
<ul dir="auto">
<li>Upgrade rspack to v2 (<a
href="55194fb">55194fb</a>)</li>
<li>Experimental rolldown config (<a
href="8c0243f">8c0243f</a>)</li>
</ul>
<h3 dir="auto">✅ Tests</h3>
<ul dir="auto">
<li>Ignore jsx test for bun/cjs (<a
href="3a744ca">3a744ca</a>)</li>
</ul>
<h3 dir="auto">❤️ Contributors</h3>
<ul dir="auto">
<li>Pooya Parsa (<a
href="https://bounce.depfu.com/github.com/pi0">@pi0</a>)</li>
<li>Kricsleo (<a
href="https://bounce.depfu.com/github.com/kricsleo">@kricsleo</a>)</li>
<li>Espen Hovlandsdal (<a
href="https://bounce.depfu.com/github.com/rexxars">@rexxars</a>)</li>
<li>Rintaro Itokawa (<a
href="https://bounce.depfu.com/github.com/re-taro">@re-taro</a>)</li>
<li>Matteo Collina (<a
href="https://bounce.depfu.com/github.com/mcollina">@mcollina</a>)</li>
<li>Mario Zechner (<a
href="https://bounce.depfu.com/github.com/badlogic">@badlogic</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="aedcdee7fe...fd3bb289b7">See
the full diff on Github</a>. The new version differs by 29 commits:</p>
<ul>
<li><a
href="fd3bb289b7"><code>chore(release):
v2.7.0</code></a></li>
<li><a
href="27fe3f2a49"><code>chore:
update release script</code></a></li>
<li><a
href="4fcd2f23aa"><code>fix:
fallback for `ENAMETOOLONG` when evaluating esm (#429)</code></a></li>
<li><a
href="8c0243f14e"><code>build:
experimental rolldown config</code></a></li>
<li><a
href="55194fbb97"><code>build:
upgrade rspack</code></a></li>
<li><a
href="0abda72c11"><code>ci:
update node test matrix</code></a></li>
<li><a
href="8c7822ef2f"><code>chore:
update tsconfig</code></a></li>
<li><a
href="08fc868c92"><code>chore:
update deps</code></a></li>
<li><a
href="5d552e3beb"><code>feat:
add `jiti/static` export (#430)</code></a></li>
<li><a
href="ae790b0214"><code>feat:
support virtual modules option (#428)</code></a></li>
<li><a
href="a3e705dbdc"><code>fix(require):
fallback to transpilation when `tryNative` fails (#413)</code></a></li>
<li><a
href="4deba16283"><code>chore:
update agents.md</code></a></li>
<li><a
href="f85b0e523d"><code>lint</code></a></li>
<li><a
href="3ce242653f"><code>feat:
support opt-in `tsconfigPaths` (#427)</code></a></li>
<li><a
href="c49c54e42e"><code>chore:
init agents.md</code></a></li>
<li><a
href="b66bd233c2"><code>feat:
add explicit resource management (using/await using) support
(#422)</code></a></li>
<li><a
href="a467d31ffc"><code>perf(interopDefault):
add caching to reduce proxy overhead by ~2x (#421)</code></a></li>
<li><a
href="fe264b49f1"><code>fix(ci):
skip `--coverage` flag for node 18</code></a></li>
<li><a
href="058d91a338"><code>chore:
lint</code></a></li>
<li><a
href="650bc48a76"><code>chore:
add missing prettier dep</code></a></li>
<li><a
href="e28b5e9645"><code>chore(deps):
update autofix-ci/action digest to 7a166d7 (#424)</code></a></li>
<li><a
href="31096aa9e2"><code>chore(deps):
update actions/checkout action to v6 (#419)</code></a></li>
<li><a
href="498e8d73a4"><code>chore:
update deps</code></a></li>
<li><a
href="9ee314fd0f"><code>test:
update</code></a></li>
<li><a
href="3a744ca271"><code>test:
ignore jsx test for bun/cjs</code></a></li>
<li><a
href="e88ac44079"><code>chore:
update deps</code></a></li>
<li><a
href="4045c7a2a2"><code>chore:
fix lint issues</code></a></li>
<li><a
href="39e86de57c"><code>chore(deps):
update actions/setup-node action to v6 (#411)</code></a></li>
<li><a
href="ddb4683d37"><code>fix(require):
passthrough resolve options (#412)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-05-13 12:02:13 +02:00
depfu[bot]
57521613ef
Update postcss 8.5.10 → 8.5.14 (patch) (#20040)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ postcss (8.5.10 → 8.5.14) ·
[Repo](https://github.com/postcss/postcss) ·
[Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/postcss/postcss/releases/tag/8.5.14">8.5.14</a></h4>

<blockquote><ul dir="auto">
<li>Fixed custom syntax regression (by <a
href="https://bounce.depfu.com/github.com/43081j">@43081j</a>).</li>
</ul></blockquote>
<h4><a
href="https://github.com/postcss/postcss/releases/tag/8.5.13">8.5.13</a></h4>

<blockquote><ul dir="auto">
<li>Fixed <code class="notranslate">postcss-scss</code> commend
regression.</li>
</ul></blockquote>
<h4><a
href="https://github.com/postcss/postcss/releases/tag/8.5.12">8.5.12</a></h4>

<blockquote><ul dir="auto">
<li>Fixed reading any file via user-generated CSS.</li>
<li>Added <code class="notranslate">opts.unsafeMap</code> to disable
checks.</li>
</ul></blockquote>
<h4><a
href="https://github.com/postcss/postcss/releases/tag/8.5.11">8.5.11</a></h4>

<blockquote><ul dir="auto">
<li>Fixed nested brackets parsing performance (by <a
href="https://bounce.depfu.com/github.com/offset">@offset</a>).</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="33b9790263...3ec13948ae">See
the full diff on Github</a>. The new version differs by 21 commits:</p>
<ul>
<li><a
href="3ec13948ae"><code>Release
8.5.14 version</code></a></li>
<li><a
href="f2bb827b20"><code>Update
dependencies</code></a></li>
<li><a
href="d75953d608"><code>Merge
pull request #2084 from 43081j/raw-raws-rawing</code></a></li>
<li><a
href="68bd2139b5"><code>fix:
always call `raw` to retrieve raw values</code></a></li>
<li><a
href="af58cf1b7a"><code>Release
8.5.13 version</code></a></li>
<li><a
href="f227dbd0e9"><code>Temporary
ignore pnpm 11 config</code></a></li>
<li><a
href="d3abd40d72"><code>Update
dependencies</code></a></li>
<li><a
href="dd06c3e113"><code>Revert
stringifier changes because of the conflict with
postcss-scss</code></a></li>
<li><a
href="ae889c815f"><code>Try
to fix CI</code></a></li>
<li><a
href="e0093e49bc"><code>Move
to pnpm 11</code></a></li>
<li><a
href="9bc81c48f0"><code>Release
8.5.12 version</code></a></li>
<li><a
href="85c4d7dab8"><code>Another
try to fix coverage</code></a></li>
<li><a
href="94484cae6d"><code>Try
to fix coverage</code></a></li>
<li><a
href="c64b7488d2"><code>Load
only .map source maps</code></a></li>
<li><a
href="aaec7b78b3"><code>Avoid
throwing JSON parsing errors for non-JSON source maps</code></a></li>
<li><a
href="233fb264ea"><code>Mention
original author of the solution</code></a></li>
<li><a
href="2502f75030"><code>Release
8.5.11 version</code></a></li>
<li><a
href="5ca1901949"><code>Speed
up parsing many nested brackets</code></a></li>
<li><a
href="42b5337dd7"><code>Update
dependencies</code></a></li>
<li><a
href="7e36e153d0"><code>Cache
node.raws locally in Stringifier hot methods</code></a></li>
<li><a
href="8ec62b157b"><code>Bypass
MapGenerator for no-source-map stringify in LazyResult</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-05-12 15:21:44 +00:00
depfu[bot]
bcd581c568
🚨 [security] Update next 16.2.4 → 16.2.6 (patch) (#20039)
<hr>

🚨 <b>Your current dependencies have known security vulnerabilities</b> 🚨

This dependency update fixes known security vulnerabilities. Please see
the details below and assess their impact carefully. We recommend to
merge and deploy this as soon as possible!
<hr>



Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ next (16.2.4 → 16.2.6) ·
[Repo](https://github.com/vercel/next.js)


<details>

<summary>Security Advisories 🚨</summary>
<h4><a
href="https://bounce.depfu.com/github.com/facebook/react/security/advisories/GHSA-rv78-f8rc-xrxh">🚨
Next.js Vulnerable to Denial of Service with Server Components</a></h4>
<blockquote><p dir="auto">A vulnerability affects certain React Server
Components packages for versions 19.x and frameworks that use the
affected packages, including Next.js 13.x, 14.x, 15.x, and 16.x using
the App Router. The issue is tracked upstream as <a
href="https://bounce.depfu.com/github.com/facebook/react/security/advisories/GHSA-rv78-f8rc-xrxh">CVE-2026-23870</a>.</p>
<p dir="auto">A specially crafted HTTP request can be sent to any App
Router Server Function endpoint that, when deserialized, may trigger
excessive CPU usage. This can result in denial of service in unpatched
environments.</p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-36qx-fr4f-26g5">🚨
Next.js has a Middleware / Proxy bypass in Pages Router applications
using i18n</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">Applications using the Pages Router with <code
class="notranslate">i18n</code> configured and middleware/proxy-based
authorization can allow unauthorized access to protected page data
through locale-less <code
class="notranslate">/_next/data/&lt;buildId&gt;/&lt;page&gt;.json</code>
requests. In affected configurations, middleware does not run for the
unprefixed data route, allowing an attacker to retrieve SSR JSON for
protected pages without passing the intended authorization checks.</p>
<h3 dir="auto">Fix</h3>
<p dir="auto">The matcher logic was updated to perform the same match as
it would on a non-i18n data route.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">If you cannot upgrade immediately, enforce authorization
in the page's server-side data path instead of relying solely on
middleware.</p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-267c-6grr-h53f">🚨
Next.js has a Middleware / Proxy bypass in App Router applications via
segment-prefetch routes</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">App Router applications that rely on middleware or
proxy-based checks for authorization can allow unauthorized access
through transport-specific route variants used for segment prefetching.
In affected configurations, specially crafted <code
class="notranslate">.rsc</code> and segment-prefetch URLs can resolve to
the same page without being matched by the intended middleware rule,
which can allow protected content to be reached without the expected
authorization check.</p>
<h3 dir="auto">Fix</h3>
<p dir="auto">We now include App Router transport variants when
generating middleware matchers, so middleware protections are applied
consistently to those requests as well as to the normal page URL.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">If you cannot upgrade immediately, enforce authorization
in the underlying route or page logic instead of relying solely on
middleware.</p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-wfc6-r584-vfw7">🚨
Next.js vulnerable to cache poisoning in React Server Component
responses</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">Applications using React Server Components can be
vulnerable to cache poisoning when shared caches do not correctly
partition response variants. Under affected conditions, an attacker can
cause an RSC response to be served from the original URL and poison
shared cache entries so later visitors receive component payloads
instead of the expected HTML.</p>
<h3 dir="auto">Fix</h3>
<p dir="auto">We now validate and interpret <code
class="notranslate">RSC</code> request headers consistently across
request classification and rendering, and we enforce the intended
cache-busting behavior so RSC payloads are not unexpectedly served from
the original URL.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">If you cannot upgrade immediately, ensure your CDN or
reverse proxy keys on the relevant RSC request headers and honors <code
class="notranslate">Vary</code>, or disable shared caching for affected
App Router and RSC responses.</p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-492v-c6pp-mqqv">🚨
Next.js has a Middleware / Proxy bypass through dynamic route parameter
injection</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">Applications that rely on middleware to protect dynamic
routes can be vulnerable to authorization bypass. In affected
deployments, specially crafted query parameters can alter the dynamic
route value seen by the page while leaving the visible path unchanged,
which can allow protected content to be rendered without passing the
expected middleware check.</p>
<h3 dir="auto">Fix</h3>
<p dir="auto">We now only honor internal route-parameter normalization
in trusted routing flows and ignore externally supplied parameter
encodings that should never have been accepted from ordinary
requests.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">If you cannot upgrade immediately, enforce authorization
in route or page logic instead of relying solely on middleware path
matching.</p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-c4j6-fc7j-m34r">🚨
Next.js vulnerable to server-side request forgery in applications using
WebSocket upgrades</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">Self-hosted applications using the built-in Node.js server
can be vulnerable to server-side request forgery through crafted
WebSocket upgrade requests. An attacker can cause the server to proxy
requests to arbitrary internal or external destinations, which may
expose internal services or cloud metadata endpoints. Vercel-hosted
deployments are not affected.</p>
<h3 dir="auto">Fix</h3>
<p dir="auto">We now apply the same safety checks to WebSocket upgrade
handling that already existed for normal HTTP requests, so upgrade
requests are only proxied when routing has explicitly marked them as
safe external rewrites.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">If you cannot upgrade immediately, do not expose the
origin server directly to untrusted networks. If WebSocket upgrades are
not required, block them at your reverse proxy or load balancer, and
restrict origin egress to internal networks and metadata services where
possible.</p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-h64f-5h5j-jqjh">🚨
Next.js has a Denial of Service in the Image Optimization API</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">When self-hosting Next.js with the default image loader,
the Image Optimization API fetches local images entirely into memory
without enforcing a maximum size limit. An attacker could cause
out-of-memory conditions by requesting large local assets from the <code
class="notranslate">/_next/image</code> endpoint that match the <code
class="notranslate">images.localPatterns</code> configuration (by
default, all patterns are allowed).</p>
<ul dir="auto">
<li>If you are using <code
class="notranslate">images.localPatterns</code>, only the patterns in
that array are impacted.</li>
<li>If you are using <code class="notranslate">images.unoptimized:
true</code>, you are NOT impacted.</li>
<li>If you are using <code class="notranslate">images.loader:
'custom'</code>, you are NOT impacted.</li>
<li>If you are using Vercel, you are NOT impacted.</li>
</ul>
<h3 dir="auto">Fix</h3>
<p dir="auto">We now apply response size limits consistently to internal
image fetches, not just external ones, and fail oversized responses
before they can exhaust process memory.</p>
<p dir="auto">This can be adjusted using the <code
class="notranslate">images.maximumResponseBody</code> configuration.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">If you cannot upgrade immediately, avoid routing large
local assets through <code class="notranslate">/_next/image</code>,
disable image optimization for large or untrusted local files, or block
image optimization access to those assets at the edge.</p>
<p dir="auto">You can disable using the <code
class="notranslate">images.localPatterns: []</code> configuration. This
will still allow fetching remote images (which is not
impacted).</p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-mg66-mrh9-m8jx">🚨
Next.js vulnerable to Denial of Service via connection exhaustion in
applications using Cache Components</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">Applications using Partial Prerendering through the Cache
Components feature can be vulnerable to connection exhaustion through
crafted POST requests to a server action. In affected configurations, a
malicious request can trigger a request-body handling deadlock that
leaves connections open for an extended period, consuming file
descriptors and server capacity until legitimate users are denied
service.</p>
<h3 dir="auto">Fix</h3>
<p dir="auto">We now treat the header used for resuming Partial
Prerendered requests as an internal-only header and strip it from
untrusted incoming requests. This header should never be accepted
directly from external clients.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">If you cannot upgrade immediately, block requests that
would be handled by Next.js if they contain the <code
class="notranslate">Next-Resume</code> header at the
edge.</p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-gx5p-jg67-6x7h">🚨
Next.js has cross-site scripting in beforeInteractive scripts with
untrusted input</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">Applications that use <code
class="notranslate">beforeInteractive</code> scripts together with
untrusted content can be vulnerable to cross-site scripting. In affected
versions, serialized script content was not escaped safely before being
embedded into the document, which could allow attacker-controlled input
to break out of the intended script context and execute arbitrary
JavaScript in a visitor's browser.</p>
<h3 dir="auto">Fix</h3>
<p dir="auto">We now HTML-escape serialized <code
class="notranslate">beforeInteractive</code> script content before
embedding it into the page, preventing attacker-controlled content from
breaking out of the inline script boundary.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">If you cannot upgrade immediately, do not pass untrusted
data into <code class="notranslate">beforeInteractive</code> scripts. If
that pattern is unavoidable, sanitize or escape the content before
embedding it.</p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-vfv6-92ff-j949">🚨
Next.js vulnerable to cache poisoning via collisions in React Server
Component cache-busting</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">React Server Component responses can be vulnerable to
cache poisoning in deployments that rely on shared caches with
insufficient response partitioning. In affected conditions, collisions
in the <code class="notranslate">_rsc</code> cache-busting value can
allow an attacker to poison cache entries so users receive the wrong
response variant for a given URL.</p>
<h3 dir="auto">Fix</h3>
<p dir="auto">We strengthened the <code class="notranslate">_rsc</code>
cache-busting mechanism to make practical collisions significantly
harder and to better separate response variants that should not share
cache entries.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">If you cannot upgrade immediately, ensure intermediary
caches correctly honor <code class="notranslate">Vary</code> for
RSC-related request headers, or disable shared caching for affected RSC
responses until you can deploy a patched release.</p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-ffhc-5mcf-pf4q">🚨
Next.js vulnerable to cross-site scripting in App Router applications
using CSP nonces</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">App Router applications that rely on CSP nonces can be
vulnerable to stored cross-site scripting when deployed behind shared
caches. In affected versions, malformed nonce values derived from
request headers could be reflected into rendered HTML in an unsafe way,
allowing an attacker to poison cached responses and cause script
execution for later visitors.</p>
<h3 dir="auto">Fix</h3>
<p dir="auto">We now reject or ignore malformed nonce values before they
are embedded into HTML and apply stricter nonce sanitization so
request-derived nonce data cannot break out of the intended attribute
context.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">If you cannot upgrade immediately, strip inbound <code
class="notranslate">Content-Security-Policy</code> request headers from
untrusted traffic.</p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-3g8h-86w9-wvmq">🚨
Next.js's Middleware / Proxy redirects can be cache-poisoned</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">Next.js uses the <code
class="notranslate">x-nextjs-data</code> request header for internal
data requests. On affected versions, an external client could send this
header on a normal request to a path handled by middleware that returns
a redirect.</p>
<p dir="auto">When that happened, the middleware/proxy could treat the
request as a data request and replace the standard <code
class="notranslate">Location</code> redirect header with the internal
<code class="notranslate">x-nextjs-redirect</code> header. Browsers do
not follow <code class="notranslate">x-nextjs-redirect</code>, so the
response became an unusable redirect for normal clients.</p>
<p dir="auto">If the application was deployed behind a CDN or reverse
proxy that caches 3xx responses without varying on this header, a single
attacker request could poison the cached redirect response for the
affected path. Subsequent visitors could then receive a cached redirect
response without a <code class="notranslate">Location</code> header,
causing a denial of service for that redirect path until the cache entry
expired or was purged.</p>
<h3 dir="auto">Affected scenarios</h3>
<p dir="auto">This affects applications that:</p>
<ul dir="auto">
<li>use middleware or proxy redirects</li>
<li>are deployed behind a caching CDN or reverse proxy</li>
<li>allow 3xx responses on those paths to be cached without
differentiating internal data requests from normal requests</li>
</ul>
<h3 dir="auto">Fix</h3>
<p dir="auto">The fix stops trusting <code
class="notranslate">x-nextjs-data</code> by itself for middleware
redirect handling. A request is now treated as an internal data request
only when it is validated as such by internal routing state, preserving
legitimate data-request redirect behavior while preventing external
header injection from changing normal redirect responses.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">Before upgrading, users can reduce risk by:</p>
<ul dir="auto">
<li>configuring the CDN or reverse proxy to vary its cache key on <code
class="notranslate">x-nextjs-data</code> for affected responses</li>
</ul></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-267c-6grr-h53f">🚨
Next.js has a Middleware / Proxy bypass in App Router applications via
segment-prefetch routes - Incomplete Fix Follow-Up</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">It was found that the fix addressing <a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-267c-6grr-h53f">CVE-2026-44575</a>
did not apply to <code class="notranslate">middleware.ts</code> with
Turbopack. Refer to <a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-267c-6grr-h53f">CVE-2026-44575</a>
for further details.</p>
<h3 dir="auto">References</h3>
<ul dir="auto">
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-267c-6grr-h53f">CVE
CVE-2026-44575</a></li>
</ul></blockquote>
</details>

<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v16.2.6">16.2.6</a></h4>

<blockquote><p dir="auto">This release contains security fixes for the
following advisories:</p>
<p dir="auto">High:</p>
<ul dir="auto">
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-8h8q-6873-q5fj">GHSA-8h8q-6873-q5fj:
Denial of Service with Server Components</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-267c-6grr-h53f">GHSA-267c-6grr-h53f:
Middleware / Proxy bypass in App Router applications via
segment-prefetch routes</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-26hh-7cqf-hhc6">GHSA-26hh-7cqf-hhc6:
Middleware / Proxy bypass in App Router applications via
segment-prefetch routes - Incomplete Fix Follow-Up</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-mg66-mrh9-m8jx">GHSA-mg66-mrh9-m8jx:
Denial of Service via connection exhaustion in applications using Cache
Components</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-492v-c6pp-mqqv">GHSA-492v-c6pp-mqqv:
Middleware / Proxy bypass through dynamic route parameter
injection</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-c4j6-fc7j-m34r">GHSA-c4j6-fc7j-m34r:
Server-side request forgery in applications using WebSocket
upgrades</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-36qx-fr4f-26g5">GHSA-36qx-fr4f-26g5:
Middleware / Proxy bypass in Pages Router applications using
i18n</a></li>
</ul>
<p dir="auto">Moderate:</p>
<ul dir="auto">
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-ffhc-5mcf-pf4q">GHSA-ffhc-5mcf-pf4q:
Cross-site scripting in App Router applications using CSP
nonces</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-gx5p-jg67-6x7h">GHSA-gx5p-jg67-6x7h:
Cross-site scripting in beforeInteractive scripts with untrusted
input</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-h64f-5h5j-jqjh">GHSA-h64f-5h5j-jqjh:
Denial of Service in the Image Optimization API</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-wfc6-r584-vfw7">GHSA-wfc6-r584-vfw7:
Cache poisoning in React Server Component responses</a></li>
</ul>
<p dir="auto">Low:</p>
<ul dir="auto">
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-vfv6-92ff-j949">GHSA-vfv6-92ff-j949:
Cache poisoning via collisions in React Server Component
cache-busting</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-3g8h-86w9-wvmq">GHSA-3g8h-86w9-wvmq:
Middleware / Proxy redirects can be cache-poisoned</a></li>
</ul></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v16.2.5">16.2.5</a></h4>

<blockquote><p dir="auto">This release contains security fixes for the
following advisories:</p>
<p dir="auto">High:</p>
<ul dir="auto">
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-8h8q-6873-q5fj">GHSA-8h8q-6873-q5fj:
Denial of Service with Server Components</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-267c-6grr-h53f">GHSA-267c-6grr-h53f:
Middleware / Proxy bypass in App Router applications via
segment-prefetch routes</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-mg66-mrh9-m8jx">GHSA-mg66-mrh9-m8jx:
Denial of Service via connection exhaustion in applications using Cache
Components</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-492v-c6pp-mqqv">GHSA-492v-c6pp-mqqv:
Middleware / Proxy bypass through dynamic route parameter
injection</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-c4j6-fc7j-m34r">GHSA-c4j6-fc7j-m34r:
Server-side request forgery in applications using WebSocket
upgrades</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-36qx-fr4f-26g5">GHSA-36qx-fr4f-26g5:
Middleware / Proxy bypass in Pages Router applications using
i18n</a></li>
</ul>
<p dir="auto">Moderate:</p>
<ul dir="auto">
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-ffhc-5mcf-pf4q">GHSA-ffhc-5mcf-pf4q:
Cross-site scripting in App Router applications using CSP
nonces</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-gx5p-jg67-6x7h">GHSA-gx5p-jg67-6x7h:
Cross-site scripting in beforeInteractive scripts with untrusted
input</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-h64f-5h5j-jqjh">GHSA-h64f-5h5j-jqjh:
Denial of Service in the Image Optimization API</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-wfc6-r584-vfw7">GHSA-wfc6-r584-vfw7:
Cache poisoning in React Server Component responses</a></li>
</ul>
<p dir="auto">Low:</p>
<ul dir="auto">
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-vfv6-92ff-j949">GHSA-vfv6-92ff-j949:
Cache poisoning via collisions in React Server Component
cache-busting</a></li>
<li><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-3g8h-86w9-wvmq">GHSA-3g8h-86w9-wvmq:
Middleware / Proxy redirects can be cache-poisoned</a></li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="2275bd8598...ee6e79b179">See
the full diff on Github</a>. The new version differs by 36 commits:</p>
<ul>
<li><a
href="ee6e79b179"><code>v16.2.6</code></a></li>
<li><a
href="afa053d9eb"><code>Turbopack:
Match proxy matchers with webpack implementation
(#93594)</code></a></li>
<li><a
href="97a154e5bb"><code>Turbopack:
Fix middleware matcher suffix (#93590)</code></a></li>
<li><a
href="83899bc891"><code>[backport]
Disable build caches for production/staging/force-preview deploys
(#93586)</code></a></li>
<li><a
href="7b222b9095"><code>[backport][test]
Pin package manager to patch versions (#93595)</code></a></li>
<li><a
href="a8dc24f1fe"><code>[backport]
Turbopack: more strict vergen setup (#93587)</code></a></li>
<li><a
href="766148f9cd"><code>v16.2.5</code></a></li>
<li><a
href="0dd94836a8"><code>fix:
add explicit checks for RSC header (#83) (#98)</code></a></li>
<li><a
href="d166096c39"><code>fix
proxy matching for segment prefetch URLs (#89) (#96)</code></a></li>
<li><a
href="9d50c0b719"><code>Strip
next-resume header from incoming requests (#92)</code></a></li>
<li><a
href="df7ab5ad72"><code>fix:
skip internal param normalization in unsupported
environments</code></a></li>
<li><a
href="ed41d1d454"><code>Move
htmlescape to shared/lib (#91)</code></a></li>
<li><a
href="b4c6705c70"><code>Ignore
malformed CSP nonce headers</code></a></li>
<li><a
href="5b194ee2d4"><code>router-server:
guard upgrade proxy against absolute-url SSRF (#77)</code></a></li>
<li><a
href="cb171d7494"><code>Fix
i18n middleware matching for default-locale data routes
(#82)</code></a></li>
<li><a
href="89e995431a"><code>[16.x]
Type hardening and performance improvements (#80)</code></a></li>
<li><a
href="66f6017f15"><code>Escape
properties for beforeInteractive scripts (#86)</code></a></li>
<li><a
href="3d98505a24"><code>[backport]
fix: preserve HTTP access fallbacks during prerender recovery
(#93470)</code></a></li>
<li><a
href="bb5ada6e38"><code>[backport]
[test] Deflake `instant-navs-devtools` (#93534)</code></a></li>
<li><a
href="f1c11203d5"><code>[backport]
Fix double-encoding of URL pathname parts in client param parsing
(#93506)</code></a></li>
<li><a
href="2d08397b3d"><code>[backport]
fix accidental test duplication (#93507)</code></a></li>
<li><a
href="75d19ecbb3"><code>[backport]
Include deployment id in `cacheHandlers` keys (#93471)</code></a></li>
<li><a
href="7ab1e2e93d"><code>CI:
Download and run self-contained datadog-ci instead of using pnpm dlx or
npx (#92546)</code></a></li>
<li><a
href="084f2bcf19"><code>[ci]:
trigger signed release commit via API (#93285)</code></a></li>
<li><a
href="a3bb370b00"><code>[ci]:
app-based release workflow (#93245)</code></a></li>
<li><a
href="6e23383c56"><code>[ci]:
add environment to publishRelease flow (#93093)</code></a></li>
<li><a
href="f40b8876e6"><code>[ci]:
remove publish token in favor of OIDC (#93065)</code></a></li>
<li><a
href="f6bda26ef9"><code>Fix
fallback route params case in app-page handler (#93109)</code></a></li>
<li><a
href="70defda2a8"><code>[ci]:
switch to GitHub runners (#93164)</code></a></li>
<li><a
href="af0e96ba23"><code>Fix
invalid HTML response for route-level RSC requests in deployment adapter
(#91541)</code></a></li>
<li><a
href="2cdb7ed34f"><code>[tests]:
fix cache-components.test.ts type error (#93113)</code></a></li>
<li><a
href="8cd3fdc111"><code>test:
scope css data-url typing to fixture (#91877)</code></a></li>
<li><a
href="6fd09bf8ab"><code>Patch
setHeader for direct route handlers (#93101)</code></a></li>
<li><a
href="688ed31e21"><code>Strengthen
_rsc cache-busting param (#92755)</code></a></li>
<li><a
href="62ef305096"><code>fix(next/image):
ensure `images.maximumResponseBody` applies to local images too
(#92920)</code></a></li>
<li><a
href="15341fdf49"><code>Ensure
x-nextjs-data header is only set during resolve (#92752)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-05-12 17:12:36 +02:00
Robin Malfait
54d6b05b44
move localRequire to else branch 2026-05-12 11:19:48 +02:00
Antoine Lethimonnier
fc432e0fd5
fix(node): prefer Module#registerHooks over Module#register (#20028)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

Fix this warning when building apps with TailwindCSS with Node 26+:
```
(node:25346) [DEP0205] DeprecationWarning: `module.register()` is deprecated. Use `module.registerHooks()` instead.
    at node:internal/util:129:11
    at Module.register (node:internal/modules/esm/loader:969:3)
    at file:///Users/antoine/Developer/my-app/node_modules/.pnpm/@tailwindcss+node@4.3.0/node_modules/@tailwindcss/node/dist/index.mjs:18:214
    at ...
```

This PR:
- Correctly prefers using `Module#registerHooks` instead of
`Module#register` by checking its availability at runtime
- Adjusts the exports of the hooks’ file by creating a synchronous
version for the new API
- Remove now unused exports from the `package.json`, relying on the
[recommended usage in the
docs](https://nodejs.org/docs/latest/api/module.html#registration-of-asynchronous-customization-hooks)
for the `Module#register` calls

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

I'd be happy to ensure this works correctly on my end before merging
this, but it's not as trivial to test locally as a logic fix. Can you
guide me through what I need to do to test this?

I extensively based my change on the docs, following those guides:
- [Fixing imports for
`Module#register`](https://nodejs.org/docs/latest/api/module.html#registration-of-asynchronous-customization-hooks)
- [Using the new `Module#registerHooks`
function](https://nodejs.org/docs/latest/api/module.html#registration-of-synchronous-customization-hooks)

(and other more detailed sections of the same docs page)

---

Fixes #19893
Closes #19907

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-05-11 14:25:28 +00:00
depfu[bot]
ae96721fc5
Update @tybys/wasm-util 0.10.1 → 0.10.2 (minor) (#20031)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​tybys/wasm-util (0.10.1 → 0.10.2) ·
[Repo](https://github.com/toyobayashi/wasm-util)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-05-11 12:09:58 +02:00
Robin Malfait
f9216b2b8c
update changelog 2026-05-08 22:18:22 +02:00
Robin Malfait
588bd7371f
4.3.0 (#20023) 2026-05-08 22:03:59 +02:00
Robin Malfait
59936c6cbb
Add tab-* utilities (#20022)
This PR adds new `tab-*` utilities.

The `tab-*` utilities set the `tab-size` property. They support positive
integer bare values, and arbitrary values:

| Class | CSS |
| -- | -- |
| `tab-2` | `tab-size: 2;` |
| `tab-[12px]` | `tab-size: 12px;` |

This also adds `tab-size` to the property order near the other text
layout properties.

## Test plan

1. Added new `tab-*` utility tests
2. Updated the intellisense snapshot
3. Other existing tests still pass
2026-05-08 21:22:07 +02:00
Robin Malfait
90a2373620
add zoom-* utilities (#20020)
This PR adds new `zoom-*` utilities.

The `zoom-*` utilities accept bare values that are transformed into `%`
based values, and arbitrary values:

| Class | CSS |
| -- | -- |
| `zoom-50` | `zoom: 50%;` |
| `zoom-[1.1]` | `zoom: 1.1;` |
| `zoom-(--value)` | `zoom: var(--value);` |


This also adds the `zoom` property after the `transform` related
properties. Initially I added it right after `scale`, because logically
they are close together. But then `zoom-*` would sit between `scale` and
other tranfsorm related properties which is a bit weird. Instead, I
moved it after `transform`.

## Test plan

1. Added new zoom based tests
2. All other tests still pass
2026-05-08 21:15:59 +02:00
Robin Malfait
2e1ccf7f11
Add scrollbar-gutter-* utilities (#20018)
This PR adds new `scrollbar-gutter-*` utilities. The API looks like
this:

| Class | CSS |
| -- | -- |
| `scrollbar-gutter-auto` | `scrollbar-gutter: auto` |
| `scrollbar-gutter-stable` | `scrollbar-gutter: stable` |
| `scrollbar-gutter-both` | `scrollbar-gutter: stable both-edges` |
2026-05-08 21:13:06 +02:00
depfu[bot]
c00da894dd
Update listhen 1.9.1 → 1.10.0 (minor) (#20017)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ listhen (1.9.1 → 1.10.0) ·
[Repo](https://github.com/unjs/listhen) ·
[Changelog](https://github.com/unjs/listhen/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/unjs/listhen/releases/tag/v1.10.0">1.10.0</a></h4>

<blockquote><p dir="auto"><a
href="https://bounce.depfu.com/github.com/unjs/listhen/compare/v1.9.1...v1.10.0">compare
changes</a></p>
<h3 dir="auto">🚀 Enhancements</h3>
<ul dir="auto">
<li>Support <code class="notranslate">extraURLs</code> and detect <a
href="https://portless.sh/">portless</a> from env by default (<a
href="https://bounce.depfu.com/github.com/unjs/listhen/pull/228">#228</a>)</li>
</ul>
<h3 dir="auto">🩹 Fixes</h3>
<ul dir="auto">
<li>Filter IPv4 link-local addresses from network interfaces (<a
href="https://bounce.depfu.com/github.com/unjs/listhen/pull/226">#226</a>)</li>
<li>Do not use fallback port in production (<a
href="https://bounce.depfu.com/github.com/unjs/listhen/pull/223">#223</a>)</li>
</ul>
<h3 dir="auto">❤️ Contributors</h3>
<ul dir="auto">
<li>Kricsleo (<a
href="https://bounce.depfu.com/github.com/kricsleo">@kricsleo</a>)</li>
<li>Tim Krajcar (<a
href="https://bounce.depfu.com/github.com/tkrajcar">@tkrajcar</a>)</li>
<li>Max (<a
href="https://bounce.depfu.com/github.com/onmax">@onmax</a>)</li>
<li>Pooya Parsa (<a
href="https://bounce.depfu.com/github.com/pi0">@pi0</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="60ba9f2ae2...33c98f262d">See
the full diff on Github</a>. The new version differs by 5 commits:</p>
<ul>
<li><a
href="33c98f262d"><code>fix:
do not use fallback port in production (#223)</code></a></li>
<li><a
href="49ef95e7a7"><code>fix:
filter IPv4 link-local addresses from network interfaces
(#226)</code></a></li>
<li><a
href="de2d49ae00"><code>chore(deps):
update all non-major dependencies (#221)</code></a></li>
<li><a
href="f341eb9f76"><code>feat:
support `extraURLs` and detect portless by env by default
(#228)</code></a></li>
<li><a
href="402c52df87"><code>chore:
update deps</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

[ci-all]

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-05-07 12:43:28 +00:00
Robin Malfait
754e7512ca
Use non-existing example in tests (#20021)
This PR makes a small change to the tests. In some cases, where we test
`@utility` functionality we use `tab-*` utilities.

But there is a possibility that we will add this as an actual utility,
so instead let's use a much more generic `example-*` utility. Then we
can read from the `--example` theme value, and use `--resolved-value:
…;` and `--resolved-modifier: …;` values.

It's admittedly more vague, but chances of conflicts go way down. Until
CSS or Tailwind CSS introduces an `example-*` utility...


## Test plan

1. Only tests changed, and all of them still pass
2026-05-07 13:23:24 +02:00
Robin Malfait
12eb5ae7b6
Cleanup noisy test output (#20015)
This PR cleans up the noisy test output where some `console.warn`
messages were leaking into the test output.

This also uses the `src/` files instead of the `dist/` files of
`@tailwindcss/node` to get rid of a source map related warning in tests.
It also means that we don't have to rebuild `@tailwindcss/node` when we
make changes.

## Test plan

1. Existing tests pass
2. Output is clean when running tests (`vitest run --reporter=minimal`)

Before:
<img width="1193" height="1376" alt="image"
src="https://github.com/user-attachments/assets/9aceab62-cd99-4391-9734-0ae5c4c3fb48"
/>

After:
<img width="1099" height="294" alt="image"
src="https://github.com/user-attachments/assets/3ada4a0a-8a7a-48a7-9fbd-f3d5dd8700a5"
/>
2026-05-06 11:10:17 +02:00
Robin Malfait
4255671c5f
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:

First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`

Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
  "@layer utilities {
    .foo {
      color: #000;
    }

    .bar {
      color: red;
    }
  }"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.

Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
  "
  @layer utilities {
    .foo {
      color: #000;
    }

    .bar {
      color: red;
    }
  }
  "
`)
```

In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
  @layer utilities {
    .foo {
      color: #000;
    }

    .bar {
      color: red;
    }
  }
`)
```

That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.

Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.

The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.

When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
  .\[color:red\] {
    color: red;
  }
`
```

But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
  .\\[color:red\\] {
    color: red;
  }
`
```

**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>

Long story short, simple `\n` and `\n` boundaries it is!

## Test plan

- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
Robin Malfait
8c779899bb
Ensure math operators are surrounded by whitespace in arbitrary values (#20011)
This PR fixes an issue where some `calc(…)` expressions become invalid
after we canonicalize them to a different syntax.

Let's say you start with:
`left-[calc(-1*(var(--my-var1)+var(--my-var2)))]`, then the produced AST
for this candidate looks like this:
```js
[
  {
    "kind": "functional",
    "root": "left",
    "modifier": null,
    "value": {
      "kind": "arbitrary",
      "dataType": null,
      "value": "calc(-1 * (var(--my-var1) + var(--my-var2)))"
    },
    "variants": [],
    "important": false,
    "raw": "left-[calc(-1*(var(--my-var1)+var(--my-var2)))]"
  }
]
```
Notice that the `+` in between the vars already contain spaces.

And the generated CSS looks like this:
```css
.left-\[calc\(-1\*\(var\(--my-var1\)\+var\(--my-var2\)\)\)\] {
  left: calc(-1 * (var(--my-var1) + var(--my-var2)));
}
```
Again, the `+` has spaces aroudn it.

However, we canoncialize this syntax where we remove the `calc(-1 *
<value>)` and move the `-` in front:
`-left-[(var(--my-var1)+var(--my-var2))]`, which should behave the same,
but it did not. The parsed value for this looks like:
```js
[
  {
    "kind": "functional",
    "root": "-left",
    "modifier": null,
    "value": {
      "kind": "arbitrary",
      "dataType": null,
      "value": "(var(--my-var1)+var(--my-var2))"
    },
    "variants": [],
    "important": false,
    "raw": "-left-[(var(--my-var1)+var(--my-var2))]"
  }
]
```
Notice that the `+` does not contain spaces around it. That's because we
add them when we parse arbitrary values and when we are in a `calc(…)`
expression. But the `calc(<value> * -1)` is added later, so at this
point, no spaces are added yet.
This also means that the generated CSS currently looks like:
```css
.-left-\[\(var\(--my-var1\)\+var\(--my-var2\)\)\] {
  left: calc((var(--my-var1)+var(--my-var2)) * -1);
}
```
Which is invalid.

To solve this, we will make sure to add the whitespace around operators
when we re-insert the `calc(<value> * -1)`. With this fix, the CSS looks
like this:
```css
.-left-\[\(var\(--my-var1\)\+var\(--my-var2\)\)\] {
  left: calc((var(--my-var1) + var(--my-var2)) * -1);
}
```
Which is correct again.

---

There are a few other issues that need a bit more work, but are not
required for this fix.
1. Can we get rid of the additional `(` and `)` parens? E.g.:
   ```diff
   - left-[calc(-1*(var(--my-var1)+var(--my-var2)))]
   - -left-[(var(--my-var1)+var(--my-var2))]
   + -left-[var(--my-var1)+var(--my-var2)]
   ```
Right now this means that we should use `calc((<value>) * -1)` instead
of
`calc(<value> * -1)` since `<value>` can be an expression on its own.
This
could lead to unwanted behavior, but this will be a follow up PR _if_
it's
   worth it.
2. Why did we even allow this canoncialization from A to B if it's not
the same result?

This last question is a bit more scary, but it has to do with how we
compare results. We create a signature where we normalize a bunch of
values to make sure that we can compare them. As a silly example
`calc(var(--a) + var(--b))` and `calc(var(--b) + var(--a))` will result
in the same value, therefor should have the same signature and should be
swappable.
But what's happening is that the signature of
`left-[calc(-1*(var(--my-var1)+var(--my-var2)))]` and
`-left-[(var(--my-var1)+var(--my-var2))]` result in:
```
/* Signature of: left-[calc(-1*(var(--my-var1)+var(--my-var2)))] */
.x {
  left: calc((var(--my-var1)+var(--my-var2))*-1);
}

/* Signature of: -left-[(var(--my-var1)+var(--my-var2))] */
.x {
  left: calc((var(--my-var1)+var(--my-var2))*-1);
}
```

This gets rid of whitespace to store less data, but this is obviously
wrong now, so we need to improve the signatures around this. That said,
that will be a follow up PR as well because this requires much more
testing.
But this PR at least fixes the #20010 issue because we will properly
insert the whitespace around the math operators.

Fixes: #20010

## Test plan

1. Added a regression test to make sure that the new canonicalized
syntax results in the correct CSS. Before the fix, the test would fail:
<img width="623" height="106" alt="image"
src="https://github.com/user-attachments/assets/c470ce38-c3fa-4080-92ca-8a3e509f700b"
/>
2. Existing tests still pass
2026-05-05 17:08:24 +02:00
Adam Wathan
b4db3b99d1
Add scrollbar-width and scrollbar-color utilities (#19981)
## Summary

Adds utilities for the
[`scrollbar-width`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scrollbar-width)
and
[`scrollbar-color`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/scrollbar-color)
CSS properties.

### `scrollbar-width`

Three static utilities mirroring the spec keywords:

| Class | CSS |
| --- | --- |
| `scrollbar-auto` | `scrollbar-width: auto;` |
| `scrollbar-thin` | `scrollbar-width: thin;` |
| `scrollbar-none` | `scrollbar-width: none;` |

### `scrollbar-color`

The `scrollbar-color` property takes two colors (thumb and track). To
allow them to be set independently, two color utilities are added that
share a pair of CSS variables (`--tw-scrollbar-thumb` and
`--tw-scrollbar-track`), following the same pattern as the gradient stop
utilities (`from-*` / `via-*` / `to-*`):

| Class | CSS |
| --- | --- |
| `scrollbar-thumb-<color>` | `--tw-scrollbar-thumb: <color>;
scrollbar-color: var(--tw-scrollbar-thumb) var(--tw-scrollbar-track);` |
| `scrollbar-track-<color>` | `--tw-scrollbar-track: <color>;
scrollbar-color: var(--tw-scrollbar-thumb) var(--tw-scrollbar-track);` |

Both go through `colorUtility`, so they automatically support the
standard color palette, theme keys (`--scrollbar-thumb-color` /
`--scrollbar-track-color` with `--color` as a fallback), arbitrary
values, and the `/<alpha>` opacity modifier. The variables are
registered with `@property` (initial value `#0000`), matching the
gradient-stop convention so an unset side falls back to transparent.

```html
<div class="scrollbar-thin scrollbar-thumb-red-500 scrollbar-track-zinc-200">…</div>
```

## Test plan

- [x] `pnpm test` (all 4480 tests pass)
- [x] New `scrollbar-width`, `scrollbar-thumb`, and `scrollbar-track`
test cases in `utilities.test.ts` covering palette colors, theme-key
colors, `current` / `inherit` / `transparent`, arbitrary colors,
`/<alpha>` modifiers, and invalid candidates
- [x] `intellisense.test.ts` snapshot updated to include the new class
names


---
_Generated by [Claude
Code](https://claude.ai/code/session_014ajg5maQ4gUHKmqBs5o7vD)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-05-04 17:16:50 +02:00
Robin Malfait
08cad84bbe
Support --default(…) in --value(…) and --modifier(…) to support fallback values (#19989)
This PR adds a new `--default(…)` option that can be used inside
`--value(…)` or `--modifier(…)` such that functional utilities without
an explicit value/modifier can still be defined as a functional utility.
It would also allow you to use a functional utility without a value and
_with_ a modifier, e.g.: `shadow/50`.

---

This allows us to re-implement functional utilities with a default value
in CSS using `@utility`.

Used the explicit `--default(…)` argument of `--value(…)` for a few
reasons.

1. It's explicit about being a falllback value. If you have `@utility
foo-*`, then you want to be able to use `foo`, but `foo-bad` should not
compile.
2. When `--value(…)` is used in (complex) property values (think a bunch
of `calc(…)` expressions), then we don't need a separate property for
this.

One of the ideas was to have a literal fallback:
```css
@utility tab-* {
  tab-size: 4;
  tab-size: --value(number);
}
```

For `tab`, this would compile to:
```css
.tab {
  tab-size: 4;
}
```

For `tab-123`, this would compile to:
```css
.tab {
  tab-size: 4;
  tab-size: 123;
}
```
Getting rid of the `tab-size: 4` would be an option, but it's a common
pattern in real CSS for fallback values (think hex background color,
over a more modern `oklch` color).

For `tab-foo`, this would compile to:
```css
.tab {
  tab-size: 4;
}
```

Which means that we have an infinite amount classes that would result in
the same class, which is bad. We could special case this one because the
internal `value` would still be `null`, but it might be too confusing.

This syntax without the `--default(…)` also means repetition of certain
properties. Add `--modifier(…)` to the mix, and there is even more
repetition going on.

Another option to consider is that the default fallback is just another
option in the `--value(…, 4)`, but if a default fallback is a keyword,
then there is a chance that this might conflict with actual keywords we
interpret.

Main motivation is to be able to re-implement utilities such as
`shadow/50` purely in CSS. It's also something we support in the JS
based APIs, but not in the CSS based one, so while it's a "new" feature,
it's more like a missing feature right now, and often a reason for
people to use the JS based APIs instead.

For consistency reasons, this is also implemented for `--modifier(…)`
such that you can use a default value there. E.g. when re-implementing
`text-sm` where a default `line-height` is set without the explicit use
of a modifier.

Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16824

## Test plan

1. Added a handful of new tests to make sure this functionality works
2. Existing tests still pass
2026-05-04 17:03:00 +02:00
Robin Malfait
0f6f7d480f
Ensure --value(…) is required in functional @utility definitions (#20005)
While working on #19989, I noticed that `--value(…)` inside functional
`@utility` definitions is not required right now.

That means that the following CSS is valid:
```css
@utility foo-* {
  color: red;
}
```

But this doesn't really makes sense, because this now accepts a value
and `foo-a`, `foo-b` and `foo-c` would generate the following CSS:
```css
.foo-a {
  color: red;
}
.foo-b {
  color: red;
}
.foo-c {
  color: red;
}
```
The `a`, `b`, and `c` are not doing anything here apart from making your
CSS bigger. So this is very likely an actual bug that you forgot to use
`--value(…)`.

Additionally, if a `--value(…)` was used, but it didn't resolve
anything, then we already properly discared the candidate.

## Test plan

1. Add test to ensure `--value(…)` is required in functional `@utility`
definitions
2. Existing tests pass
2026-05-04 16:44:28 +02:00
Robin Malfait
6e2b60e84a
Do not generate CSS for start and end (#20003)
This PR fixes a bug where CSS was generated for `start` and `end`. This
was accidentally introduced when we moved the `start-*` and `end-*`
utilities to the legacy utilities. But this meant that we now generate
CSS for `start` and `end` even if no value is provided.

Fixes: #20002

## Test plan

1. Updated the tests to make sure of `--spacing: 0.25rem` which made the
tests fail, and are fixed again by applying the fix.
2. Other tests are still passing
2026-05-03 01:00:56 +02:00
depfu[bot]
4b5d6a5943
Update enhanced-resolve 5.20.1 → 5.21.0 (minor) (#19998)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ enhanced-resolve (5.20.1 → 5.21.0) ·
[Repo](https://github.com/webpack/enhanced-resolve) ·
[Changelog](https://github.com/webpack/enhanced-resolve/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/webpack/enhanced-resolve/releases/tag/v5.21.0">5.21.0</a></h4>

<blockquote><h3 dir="auto">Minor Changes</h3>
<ul dir="auto">
<li>
<p dir="auto">Added promise API and support to resolve without <code
class="notranslate">context</code> and <code
class="notranslate">resolveContext</code>. (by <a
href="https://bounce.depfu.com/github.com/alexander-akait">@alexander-akait</a>
in <a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/520">#520</a>)</p>
</li>
<li>
<p dir="auto">Add <code
class="notranslate">extensionAliasForExports</code> option. When <code
class="notranslate">true</code>, <code
class="notranslate">extensionAlias</code> also applies to paths resolved
through the <code class="notranslate">package.json</code> <code
class="notranslate">exports</code> field. Off by default to match
Node.js; opt in for full TypeScript-resolver parity with packages that
ship <code class="notranslate">.ts</code> sources alongside the compiled
<code class="notranslate">.js</code> they declare in <code
class="notranslate">exports</code>. (by <a
href="https://bounce.depfu.com/github.com/alexander-akait">@alexander-akait</a>
in <a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/554">#554</a>)</p>
</li>
</ul>
<h3 dir="auto">Patch Changes</h3>
<ul dir="auto">
<li>
<p dir="auto">Properly handle DOS device paths (<code
class="notranslate">\\?\…</code> and <code
class="notranslate">\\.\…</code>). (by <a
href="https://bounce.depfu.com/github.com/alexander-akait">@alexander-akait</a>
in <a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/551">#551</a>)</p>
</li>
<li>
<p dir="auto">Prevent fallback to parent node_modules when the <code
class="notranslate">exports</code> field target file is not found. (by
<a href="https://bounce.depfu.com/github.com/xiaoxiaojx">@xiaoxiaojx</a>
in <a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/495">#495</a>)</p>
</li>
<li>
<p dir="auto">Imports field spec deviation: non-relative targets (e.g.
<code class="notranslate">"#a": "#b"</code>) no longer re-enter imports
resolution, aligning with the Node.js ESM spec where <code
class="notranslate">PACKAGE_IMPORTS_RESOLVE</code> does not recursively
resolve <code class="notranslate">#</code> specifiers. (by <a
href="https://bounce.depfu.com/github.com/xiaoxiaojx">@xiaoxiaojx</a> in
<a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/503">#503</a>)</p>
<p dir="auto">Previously <code class="notranslate">{ "#a": "#b", "#b":
"./the.js" }</code> would chain-resolve <code
class="notranslate">#a</code> to <code
class="notranslate">./the.js</code>; now it correctly fails, matching
Node.js behavior.</p>
</li>
<li>
<p dir="auto">Move <code class="notranslate">cachedJoin</code>/<code
class="notranslate">cachedDirname</code>/<code
class="notranslate">createCachedBasename</code> caches from module-level
globals to per-Resolver instances. This prevents unbounded memory growth
in long-running processes — when a Resolver is garbage collected, its
join/dirname/basename caches are released with it. (by <a
href="https://bounce.depfu.com/github.com/xiaoxiaojx">@xiaoxiaojx</a> in
<a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/507">#507</a>)</p>
</li>
<li>
<p dir="auto">Fixed when <code class="notranslate">tsconfig: true</code>
is used (default config file) and no <code
class="notranslate">tsconfig.json</code> exists. (by <a
href="https://bounce.depfu.com/github.com/xiaoxiaojx">@xiaoxiaojx</a> in
<a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/502">#502</a>)</p>
</li>
<li>
<p dir="auto">Apply the <code class="notranslate">extensionAlias</code>
option to the <code class="notranslate">imports</code> field to be align
with typescript resolution. (by <a
href="https://bounce.depfu.com/github.com/alexander-akait">@alexander-akait</a>
in <a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/549">#549</a>)</p>
</li>
<li>
<p dir="auto">Improved performance of the many plugins. (by <a
href="https://bounce.depfu.com/github.com/alexander-akait">@alexander-akait</a>
in <a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/529">#529</a>)</p>
</li>
<li>
<p dir="auto">Replace the <code
class="notranslate">Set&lt;string&gt;</code>-based resolver stack with a
singly-linked <code class="notranslate">StackEntry</code> class that
exposes a Set-compatible API. (by <a
href="https://bounce.depfu.com/github.com/xiaoxiaojx">@xiaoxiaojx</a> in
<a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/526">#526</a>)</p>
<p dir="auto">Each <code class="notranslate">doResolve</code> call now
prepends a single linked-list node instead of cloning the entire Set,
making stack push O(1) in time and memory. Recursion detection walks the
linked list (O(n)), but because the stack is typically shallow this is
much cheaper than cloning a Set per call.</p>
</li>
<li>
<p dir="auto">Cache the result of <code
class="notranslate">stripJsonComments</code> + <code
class="notranslate">JSON.parse</code> in <code
class="notranslate">readJson</code> using a <code
class="notranslate">WeakMap</code> keyed by the raw file buffer. This
avoids redundant comment-stripping and JSON parsing on every resolve
call that reads tsconfig.json files (via <code
class="notranslate">stripComments: true</code>), improving
TsconfigPathsPlugin warm performance by ~20-35% depending on the depth
of the <code class="notranslate">extends</code> chain. (by <a
href="https://bounce.depfu.com/github.com/xiaoxiaojx">@xiaoxiaojx</a> in
<a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/524">#524</a>)</p>
</li>
<li>
<p dir="auto">Avoid OOM in CachedInputFileSystem when duration is
Infinity. (by <a
href="https://bounce.depfu.com/github.com/alexander-akait">@alexander-akait</a>
in <a
href="https://bounce.depfu.com/github.com/webpack/enhanced-resolve/pull/527">#527</a>)</p>
</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="ebc67d3896...35035ca158">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-05-01 11:29:19 +00:00
Robin Malfait
52f94c74bb
Improve codebase quality (#19999)
Small PR that improves the overall quality of the codebase. It's a bit
of everything:

1. Using correct variants for the variants we are testing
2. Use `@reference` instead of `@import` in a test, testing the
`@reference` according to the test name
3. Use `using` for Vitest related mocks. They have a `Symbol.dispose`
implemented, so we can don't have to restore mocks ourselves (right now,
some of them are not cleaned up at all).
4. Updated deprecated `.toThrowError` with `.toThrow` APIs

## Test plan

Everything still passes.

[ci-all]
2026-04-30 23:32:37 +02:00
Robin Malfait
0db856f3de
Make integration tests more stable in CI (#19995)
This PR is an attempt to make CI more stable related to integration
tests.

[ci-all]
2026-04-30 23:23:36 +02:00
Robin Malfait
1ca0aacdae
Add source map visualization for tests (#19997)
This PR is an internal change only related to how we visualize source
maps.

As part of this PR
(https://github.com/tailwindlabs/tailwindcss/pull/19996) I added a test
for source maps related to how `@variant` is processed. And while the
result was correct, I had a hard time verifying if this was _actually_
correct. I did the mental mapping of comparing locations from the output
to the input.
<img width="495" height="495" alt="image"
src="https://github.com/user-attachments/assets/2b1c12cd-43ee-461a-b93b-bafe6ce1cce5"
/>

With this PR, I want to make that more visual by actually printing the
input source(s) and output file and highlight the necessary parts:
<img width="1101" height="1085" alt="image"
src="https://github.com/user-attachments/assets/14390026-f211-4cfc-8c6c-3293105f6403"
/>

I didn't want to get too clever here. But printing line numbers also
helps in case we point to different spots on the same line:
<img width="1200" height="981" alt="image"
src="https://github.com/user-attachments/assets/e75f7622-eb51-49ee-928e-fdc8672d2c3f"
/>

And if we point to different files, then we visualize these as well:
<img width="851" height="957" alt="image"
src="https://github.com/user-attachments/assets/77e65801-b46b-4579-8659-d919a8750f60"
/>


If you combine this with snapshot tests, then it's very easy to verify
that locations match up correctly. Each source map location is
highlighted and references a symbol starting at `A`, `B`, etc.

A change to the source maps _can_ result in a big diff, and even this PR
introduces a big diff because of the preflight diffs. But at least you
can see how things line up.

## Test plan

1. All tests still pass
2. Added tests for the source map visualizer that is only used in tests
3. No actual source code was touched
2026-04-30 14:52:09 +02:00
Robin Malfait
e1201bc6e3
Simplify @variant usage, allow compound and stacked variants (#19996)
This PR improves and simplifies the `@variant` usage. 

When we originally added support for `@variant`, we wanted to keep
things simple, where we could only use a single variant at a time. The
original PR did have a more complex system with all these features
enabled, but we wanted to make sure that we only introduced the
additional complexity when the community felt like it was needed.

But of course we still wanted to make sure that you could do compound
and stacked variants, it just required some additional code.

For compound variants, where you want to use variant `a` and variant
`b`, you could duplicate the rules as siblings:
```css
.foo {
  @variant a {
    display: flex;
  }

  @variant b {
    display: flex;
  }
}
```

But with this PR, you can comma separate each variant to get the same
effect:
```css
.foo {
  @variant a, b {
    display: flex;
  }
}
```
You can think of this as-if we are expanding this syntax into the
aforementioned syntax. In other words, we would do the duplication for
you.

Additionally, you also want to be able to stack variants. For that you
had to nest your `@variant` rules:
```css
.foo {
  @variant a {
    @variant b {
      display: flex;
    }
  }
}
```

Not the end of the world, but it can get pretty nested if you want to
use multiple variants. Luckily we already have a syntax for this in
normal Tailwind CSS classes: `a🅱️flex`. Which is exactly what we can
use here as well:
```css
.foo {
  @variant a:b {
    display: flex;
  }
}
```
Again, conceptually you can think of this syntax being expanded into the
syntax from above.

Last but not least, we can also combine these:
```css
.foo {
  background: black;

  @variant a, b:c {
    background: red;

    @variant d, e:f {
      background: blue;
    }
  }
}
```
This conceptually translates into the much more verbose version today:
```css
.foo {
  background: black;

  @variant a {
    background: red;

    @variant d {
      background: blue;
    }

    @variant e {
      @variant f {
        background: blue;
      }
    }
  }

  @variant b {
    @variant c {
      background: red;

      @variant d {
        background: blue;
      }

      @variant e {
        @variant f {
          background: blue;
        }
      }
    }
  }
}
```

The biggest downside is that this could potentially easily balloon your
CSS file size if you're not careful. Because with this, it's pretty easy
to add one more variant that introduces a lot of duplicated CSS.

This feature is completely backwards compatible, you can still nest your
`@variant` calls yourself if you want, and combine them with these
features if you want.

This is also a continuation of #19526 and #19884, but for some reason I
don't have push rights, so I'm creating this new PR instead. I did keep
the original commits of those PRs so these contributors are still
properly marked as contributors.
<img width="808" height="135" alt="image"
src="https://github.com/user-attachments/assets/bee334ab-39d7-4d4d-a48f-afa2253cf17b"
/>
<img width="349" height="75" alt="image"
src="https://github.com/user-attachments/assets/fb68906c-db74-43f7-83e4-918ad3d4a036"
/>


Closes: #19526
Closes: #19884

## Test plan

1. Added a bunch of new tests to verify this new behavior
2. Added tests that compare the short (new) version, and the long (old)
version
3. Added a sourcemap related test to ensure that the src and dst
locations are correct
4. Existing tests still pass

---------

Co-authored-by: orteth01 <tortega128@gmail.com>
Co-authored-by: Ray Knight <array.knight+github@gmail.com>
2026-04-30 12:19:44 +02:00
silverwind
6cf1af26b3
Fix TS2742 error when inferring exported Config type (#19707)
Type aliases get resolved through to `UserConfig` during declaration
emit, which lives in a hashed internal DTS chunk with no stable import
path, causing TS2742. This is 100% backwards-compatible because type
aliases and interfaces are structurally comparable.

Fixes: https://github.com/tailwindlabs/tailwindcss/issues/15844
Fixes: https://github.com/tailwindlabs/tailwindcss/issues/19706
Fixes: https://github.com/tailwindlabs/tailwindcss/discussions/15458

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-04-29 18:48:08 +00:00
Matt Van Horn
f036f80195
Allow multiple @utility definitions with same name but different value types (#19777)
## Summary

Fixes #16948

When defining multiple CSS `@utility foo-*` with different value types
(e.g., one for colors, one for numbers), only the first handler was
tried. If it returned `null` (value didn't match), the compile loop
stopped, preventing subsequent handlers from being attempted.

```css
@utility foo-* {
  color: --value(--color-*);
}
@utility foo-* {
  font-size: --spacing(--value(number));
}
```

Previously, `foo-red-500` worked but `foo-123` did not (or vice versa
depending on definition order).

The fix distinguishes between CSS `@utility` handlers and JS plugin
`matchUtilities` handlers:
- **CSS `@utility`** (no typed options): `null` means "try the next
handler" - allows multiple definitions with different value types to
coexist
- **JS `matchUtilities`** (with explicit types): `null` means "the value
was invalid for this type, stop" - preserves existing behavior where
typed utilities prevent invalid values from falling through

## Test plan

- Added test: two `@utility foo-*` definitions with different value
types - verifies both `foo-red-500` (color) and `foo-123` (number)
produce correct CSS
- All 4621 existing tests pass (including the `matchUtilities`
type-safety tests)
- `pnpm build && pnpm test` passes

This contribution was developed with AI assistance (Claude Code).

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-04-29 14:56:17 +00:00
Abhijeet Abhi
d194d4c3e6
docs: fix various typos in comments and documentation (#19878)
Fixes a few minor typos across the codebase (e.g. 'overriden' ->
'overridden', 're-use' -> 'reuse').

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-04-29 13:28:39 +00:00
Daniel Polito
ba667400e8
[@tailwindcss/upgrade] Don’t migrate inline style properties (#19918)
Prevent the upgrade tool from rewriting CSS properties inside inline
`style` attributes.

This fixes cases like `style="flex-grow: 1"` being changed to
`style="grow: 1"` and adds regression tests.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-04-29 13:18:02 +00:00
Robin Malfait
73f3a6a743
Canonicalization: migrate [&:has(…)] to has-[…] variant (#19991)
This PR improves the canonicalization around arbitrary variants
containing `[&:has(…)]`, by converting them to `has-[…]`.

Essentially when you have a variant, with `&:has(…)`, we will convert
it:

```diff
- [&:has([role=checkbox])]:flex
+ has-[[role=checkbox]]:flex
```

This also means that if the arbitrary selector inside of the `&:has(…)`
can be converted to something that aligns with known variants, then we
will do that as well:

```diff
  // `data-*` can be hoisted outside of the arbitrary value
- [&:has([data-slot=description])]:flex
+ has-data-[slot=description]:flex

  // `aria-visible="true"` can be entirely replaced by `aria-visible`
- [&:has([aria-visible="true"])]:flex
+ has-aria-visible:flex
```

Noticed this while looking into:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1562

## Test plan

1. Added additional tests for this use case
2. Existing tests still pass
2026-04-29 14:20:11 +02:00
Robin Malfait
b9449612ba
Canonicalization: do not canonicalize units in arbitrary values (#19988)
This PR improves the canonicalization when dealing with arbitrary
values.

As part of the canonicalization process we compute a signature for a
given utility. This way we can ensure that when we canonicalize a
candidate into a simpler candidate that it's still equivalent if the
signatures match.

One thing we do during signature computation is normalizing dimensions
(value + unit) into the same unit to make comparisons easier.

For example:
```css
.foo { margin-top: 20in; }
.bar { margin-top: 1920px; }
```

Will both get converted to `1920px` and therefore `foo` and `bar` will
have the same signature.

Up until this part, everything is fine. However, this normalization also
leaks when we try to canonicalize arbitrary values. One of the things we
do is try to move the `-` into the arbitrary value:
```diff
- -mt-[20in]
+ mt-[calc(20in_*_-1)]
```

This is obviously not cleaner, but we can perform some canonicalization
of the arbitrary value. As part of that we do constant folding _and_ the
normalization of base units. That means that we would see this:
```diff
- -mt-[20in]
- mt-[calc(20in_*_-1)]
+ mt-[-1920px]
```

But this might be very confusing because it might not make sense where
the `1920px` even came from... the only thing we should have done here
is constant fold that calc expression.

That's what this PR does, it only does the constant folding but without
the unit normalization:
```diff
- -mt-[20in]
- mt-[calc(20in_*_-1)]
+ mt-[-20in]
```

Which is exactly what we want!

Fixes:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1573

## Test plan

1. Added a regression test based on the linked issue
2. All other tests still pass
2026-04-28 16:42:02 +02:00
Robin Malfait
3aac5dafe8
Improve whitespace handling during canonicalization (#19986)
This PR fixes a printing bug during canonicalization where it converts:
```
[&:has(~_*_*:checked)]:text-green-500
```

into:
```
[&:has(~**:checked)]:text-green-500
```

This is because the `_` was marked as insignificant and therefore
removed. This PR fixes that and maintains the whitespace (`_`)
characters when needed.

Additionally, in the comments of the linked issue somebody mentioned
that:
```
w-[calc(100%_-_--spacing(60))]
```

was turned into:
```
w-[calc(100%---spacing(60))]
```

...and while that's still correct and parseable, it's not the prettiest.

This PR will still get rid of the whitespace, but introduce wrapping
parens `(…)` instead, in case readability is not ideal.

In this case, we will turn it into:
```diff
- w-[calc(100%_-_--spacing(60))]
- w-[calc(100%---spacing(60))]
+ w-[calc(100%-(--spacing(60)))]
```

Of course there are some cases where we don't need to introduce `(…)`
unnecessarily:

- `shadow-[inset_0px_1px_--theme(--color-white/15%)]` would not be
turned into `shadow-[inset_0px_1px_(--theme(--color-white/15%))]`
because no readability is gained when it's part of a normal space
separated list
- `m-[--spacing(12.34)]` would not be turned into
`m-[(--spacing(12.34))]` because there is nothing else it can conflict
with
- `m-[calc(--spacing(12.34)*2)]` would not be turned into
`m-[calc((--spacing(12.34))*2)]` because it's the first argument and
doesn't conflict with the `*`
- `m-[min(100%,--spacing(12.34))]` would not be turned into
`m-[min(100%,(--spacing(12.34)))]` because a `,` doesn't cause
readability issues


Fixes:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1544

## Test plan

1. Added new tests to ensure the bug is fixed
2. Added new tests to ensure readability is improved (and not degraged)
when whitespace was used to improve readability
2026-04-27 00:20:53 +02:00
Robin Malfait
107e451e37
Improve CI for NAPI-RS (#19983)
This PR updates the CI workflows related to NAPI-RS. So far, we've been
using a custom ghcr.io based image, but these run on Node v18.

With this PR, I created a new project using `napi new`, with GitHub
Actions workflow setup. Then essentially ported the changes from that
new project to this project (as minimal as possible).

The main goal was to be able to bump NAPI-RS related dependencies _and_
get an insiders release out. The images used in these CI jobs were
blockers to get an actual (insiders) release out.

## Test plan

1. CI still passes
2. Added a dry-run on the prepare release workflow to test that we got
through the entire workflow without issues
2026-04-26 19:37:28 +02:00
Robin Malfait
615835d1f5
Bump NAPI related dependencies (#19982)
This PR bumps all the NAPI related dependencies

Closes: #19977
Closes: #19976
Closes: #19973
Closes: #19972
Closes: #19971
Closes: #19970

## Test plan

1. All tests still pass

[ci-all] to verify on Windows and macOS as well
2026-04-26 17:49:06 +02:00
It's Me!
bfb5732b0b
Fall back to the plugin base when PostCSS has no from option (#19980)
## Summary

`@tailwindcss/postcss` derives `inputBasePath` from `result.opts.from`:

```ts
let inputFile = result.opts.from ?? ''
let inputBasePath = path.dirname(path.resolve(inputFile))
```

When PostCSS calls the plugin without `from` (some bundlers, including
Turbopack, do this for certain CSS inputs), `inputFile` is `''`,
`path.resolve('')` returns `process.cwd()`, and `path.dirname(...)`
therefore returns the **parent of CWD**. The downstream `compileAst({
base: inputBasePath })` call then asks the resolver to find
`tailwindcss` from one level above the project root, which fails with:

```
Can't resolve 'tailwindcss' in '<parent of CWD>'
```

The plugin already computes `base = opts.base ?? process.cwd()` near the
top. Reusing that as the fallback gives a sensible default (CWD) and
respects an explicit `opts.base` when set.

```diff
-let inputBasePath = path.dirname(path.resolve(inputFile))
+let inputBasePath = inputFile
+  ? path.dirname(path.resolve(inputFile))
+  : base
```

## Test plan

Added a test in `packages/@tailwindcss-postcss/src/index.test.ts` that
processes `@import 'tailwindcss'` via `processor.process(input)` with no
`from` option. Before the fix, this throws `Error: Can't resolve
'tailwindcss' in '<parent of CWD>'`; after the fix, the import resolves
and the processor returns non-empty CSS.

I wasn't able to run the suite locally — `pnpm build` requires `cargo`
for `@tailwindcss/oxide` and I don't have a Rust toolchain set up — so
the test has been written to match existing conventions in
`index.test.ts` (vitest, plain
`postcss([tailwindcss({...})]).process(...)`), and I'm relying on CI to
verify.

---------

Co-authored-by: rebasecase <rebasecase@localhost>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-04-26 13:45:39 +00:00
Robin Malfait
3a890c3572
Bump dependencies (#19957)
This PR bumps dependencies in all the packages, typically just bumping
to the latest patch release.


Closes: #19936
Closes: #19917
Closes: #19899
Closes: #19897
Closes: #19845
Closes: #19832
Closes: #19967
Closes: #19968 

## Test plan

1. All tests still pass
2. All integration tests still pass

[ci-all] to verify Linux, Windows and macOS
2026-04-24 21:21:12 +02:00
Tommy D. Rossi
db27049caa
fix(@tailwindcss/vite): include @variant in feature detection (#19966)
Fixes #19964

CSS files imported via JS that only use `@variant` (no `@apply`,
`theme()`, or utility classes) are silently skipped by the Vite plugin.
The `@variant` directive gets passed through raw to the browser, which
drops it as an unknown at-rule.

**Root cause** — `Features.Variants` is missing from the [feature
detection
bitmask](https://github.com/tailwindlabs/tailwindcss/blob/v4.2.4/packages/%40tailwindcss-vite/src/index.ts#L526):

```ts
// before
Features.AtApply | Features.JsPluginCompat | Features.ThemeFunction | Features.Utilities

// after
Features.AtApply | Features.JsPluginCompat | Features.ThemeFunction | Features.Utilities | Features.Variants
```

**Reproduction** — https://github.com/remorses/tailwind-variant-bug

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-04-24 14:11:48 +00:00
Robin Malfait
5a799900d4
Always resolve relative files, relative to the current .css file (#19965)
This PR fixes an issue where resolving of certain CSS or JS files
results in the wrong paths. The issue happens if you have a setup where
a relative file path _also_ exists in the parent folder:

```css
/* src/foo.css */
.foo-in-root {}

/* src/theme/a.css */
@import "./foo.css"; /* This resolved to the file above, instead of the file below */

/* src/theme/foo.css */
.foo-in-theme {}
```

This happened because we resolved relative to a `base` folder, but Vite
expects an `importer` instead. The difference is subtle, but they expect
a file. On that file they use `let base = path.dirname(importer)` to get
a base path out themselves.

This in turn means that if you pass in a folder, you get this:
```js
path.dirname('/path/to/my-project') // /path/to
```

If we gave it a proper file, then we get the proper base path
```js
path.dirname('/path/to/my-project/index.css') // /path/to/my-project
```

I'm actually surprised that this didn't cause issues earlier... but it
did result in error since we recently started resolving files using
Vite's `aliasOnly: true` feature such taht Vite aliases work as well.

With this change, we now make sure that:

1. We use a proper `importer` instead of the `base` path
2. We refactor the resolving logic such that we try with `aliasOnly:
true` first, then `aliasOnly: false`

We also still ensure that in the CSS resolver we expect a `.css` file,
and in the JS resolver we _don't_ expect a `.css` file (which can happen
if a `"browser": "./dist/index.css"` field in package.json points to a
CSS file, daisyUI does this for example).

Fixes: #19956

## Test plan

1. Added additional (failing) integration tests to reproduce the linked
issue
2. Existing integration tests pass

While the build still worked, the linked issue resulted in a much bigger
file size because the wrong .css files were included. With this fix, the
number is correct again:
<img width="1234" height="1037" alt="image"
src="https://github.com/user-attachments/assets/fdff803c-0db2-4066-92fa-064c2816b35c"
/>

Since this is touching code related to previous PRs, I wanted to
manually make sure that these still work as expected:

- https://github.com/tailwindlabs/tailwindcss/issues/19950: This one is
about daisyUI and the `.css` file referenced in the package.json's
`"browser"` field: <img width="782" height="1229" alt="image"
src="https://github.com/user-attachments/assets/fb7b9d3b-527b-414b-94b0-2be7e96056f1"
/>

- https://github.com/tailwindlabs/tailwindcss/issues/19946: This one is
about the Vite alias being just a single `@` causing issues with
`@plugin "@tailwindcss/typography";` for example: <img width="1694"
height="1856" alt="image"
src="https://github.com/user-attachments/assets/d1935ecf-e80a-4945-a69a-ec3c5223efab"
/>


[ci-all]
2026-04-24 15:07:56 +02:00
Robin Malfait
f62597a82c
Bump dependencies in playgrounds (#19954)
This PR bumps dependencies in our playgrounds.

Closes: https://github.com/tailwindlabs/tailwindcss/pull/19952
Closes: https://github.com/tailwindlabs/tailwindcss/pull/19945

### Test plan

Ran each playground separately to make sure they are still working
2026-04-23 12:51:20 +02:00
ArcherGu
f3fdda2a5c
fix(vite): avoid resolving JS plugins to browser CSS entries (#19949)
Edit: some edits by @RobinMalfait 

---

## Summary

Fix a regression in `@tailwindcss/vite` introduced by `#19803` where JS
plugin resolution could incorrectly resolve a package to its `browser`
CSS entry.

In cases like `daisyui`, Vite can resolve `@plugin "daisyui"` to
`daisyui.css` instead of the package's JS entry, which causes Tailwind
to try to load a CSS file as a JS plugin and fail with:

```txt
Unknown file extension ".css"
```

This change keeps the `aliasOnly: false` behavior from `#19803` so
tsconfig path resolution still works, but adds a JS-entry guard to
`customJsResolver` in `@tailwindcss/vite`. If Vite resolves a plugin
request to a non-JS file like `.css`, the custom resolver now returns
`undefined` so Tailwind's internal fallback resolver can resolve the
package as a JS plugin entry instead.

I also added integration coverage for a package whose `main`/`module`
points to JS while `browser` points to CSS, and verified that `@plugin
"pkg"` still resolves to the JS entry in both build and dev mode.

## Test plan

Added new integration tests in `integrations/vite/resolvers.test.ts`
covering a package with:

- `main` / `module` -> JS
- `browser` -> CSS
- `@plugin "pkg"` -> should resolve to JS, not CSS

Verified with:

```sh
pnpm test:integrations vite/resolvers.test.ts -t "browser points to CSS"
pnpm test:integrations vite/resolvers.test.ts -t "resolves tsconfig paths"
```

These verify that:

- `@plugin` no longer resolves to a CSS browser entry
- the original tsconfig paths fix from `#19803` still works in both
build and dev mode

---

Maintainer edits:

Instead of hardcoding file extensions, first try to resolve aliases and
then fallback to the default resolving system we had before. We still
check for a `.css` extension, even in the JS resolver because some
dependencies (like `daisyUI`) put the CSS file there instead of in an
`exports.style`. If we detect that, we still fallback to the default
resolving logic.

This should be compatible with the original issue we were trying to fix
where we wanted to make Vite aliases work.

Fixes: #19950

[ci-all]

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-04-22 17:25:01 +02:00
Robin Malfait
69ad7cc5ec
4.2.4 (#19948) 2026-04-21 14:52:34 +02:00
Robin Malfait
685c19e266
Fix issue around resolving paths in @tailwindcss/vite (#19947)
This PR fixes an issue when using `@tailwindcss/vite` and you're trying
to resolve paths. In the latest 4.2.3 release, we added support for
following the vite `aliases` option.

However, some people run into issues because if you just use `@` as an
alias then using `@tailwindcss/typography` wouldn't resolve because it's
a package, and not something local.

With this PR we fix that by making sure that Vite's resolver can
actually resolve to an absolute path. If not, then we fallback to the
resolving that happens in `@tailwindcss/node`.

Fixes: #19946

## Test plan

1. Added an integration test that reproduces this issue, and is now
solved
2. Tested it on a reproduction provided in the corresponding issue

Before:
<img width="1694" height="1856" alt="image"
src="https://github.com/user-attachments/assets/c50f7104-78b3-476d-9e60-0c83e0976a5c"
/>


After:
<img width="1694" height="1856" alt="image"
src="https://github.com/user-attachments/assets/44ea0e3b-f60f-479f-a4a1-203a6230475b"
/>
2026-04-21 14:11:46 +02:00
Robin Malfait
2e3fa490a5
4.2.3 (#19944) 2026-04-20 22:32:52 +02:00
Pavan Shinde
4527123f68
docs(postcss): remove duplicated optimize example from README (#19938)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->
Removed a duplicated optimize: { minify: false } example from
@tailwindcss/postcss README (doc-only, one file).

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->
Check the duplicate block is removed, the earlier optimize example still
exists, and only packages/@tailwindcss-postcss/README.md changed.
2026-04-20 17:22:33 +02:00
Robin Malfait
77801e7c62
use pnpm@9.6.0 2026-04-20 17:02:28 +02:00
Robin Malfait
039bd7d82f
Setup OIDC publishing (#19943)
This PR merges the `release-insiders.yml` and `release.yml` such that we
can setup OIDC publishing to npmjs.com.
2026-04-20 16:59:31 +02:00
Robin Malfait
998a6be85d
format 2026-04-20 13:12:24 +02:00
Steffen Deusch
5a835e1728
support NODE_PATH in standalone build (#19617)
References https://github.com/tailwindlabs/tailwindcss/pull/19391.
References https://github.com/tailwindlabs/tailwindcss/pull/16274.

Right now, when using the standalone build of the TailwindCSS CLI, you
cannot use a custom `NODE_PATH`, but you can when using it via Node.js
directly.

A custom NODE_PATH allows you to resolve imports from multiple
locations. For example, in [Phoenix
LiveView](https://github.com/phoenixframework/phoenix_live_view/), we
have a feature where you can write scripts in templates that we extract
at compile time to a custom folder and users can import those in their
application bundle by saying

```javascript
import { hooks as colocatedHooks } from "phoenix-colocated/my_app"
```

where the "phoenix-colocated" folder lives in a different location than
the usual `node_modules` folder. This works fine with the default
esbuild setup, as it respects `NODE_PATH`, so we can pass it a custom
location.

We want to also support colocating CSS in templates soon, but the same
approach doesn't work with the standalone Tailwind CLI we ship with
default Phoenix projects. It works when running Tailwind through
Node.js, but we don't want to tell users they need to install it, just
to use the feature.

This patch changes the lookup logic for the standalone CLI to also
account for `NODE_PATH`. Note that you can pass multiple paths, that are
split according the the OS PATH separator.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-04-17 18:29:18 +02:00
Pavan Shinde
aad601711f
docs/fix-lightning-css-typo-postcss-readme (#19913)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary
Fix a typo in the @tailwindcss/postcss README by changing Lighting CSS
to Lightning CSS in the optimize option documentation.
<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

## Test plan
Verified the README now says Lightning CSS and that the diff is
docs-only.
<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->
2026-04-07 11:31:10 +02:00
depfu[bot]
e01946f8b7
Update @vitejs/plugin-react 5.1.3 → 5.2.0 (minor) (#19826)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (5.1.3 → 5.2.0) ·
[Repo](https://github.com/vitejs/vite-plugin-react) ·
[Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)



<details>
<summary>Release Notes</summary>

<h4>5.1.4 (from changelog)</h4>
<blockquote><h3 dir="auto">Fix <code
class="notranslate">canSkipBabel</code> not accounting for <code
class="notranslate">babel.overrides</code> (<a
href="https://bounce.depfu.com/github.com/vitejs/vite-plugin-react/pull/1098">#1098</a>)</h3>
<p dir="auto">When configuring <code
class="notranslate">babel.overrides</code> without top-level plugins or
presets, Babel was incorrectly skipped. The <code
class="notranslate">canSkipBabel</code> function now checks for <code
class="notranslate">overrides.length</code> to ensure override
configurations are processed.</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>













---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-04-03 19:28:21 +00:00
Robin Malfait
2093a72e2d
Bump Next.js and drop ESLint (#19898)
This PR bumps Next.js and drops ESLint. 

We only had ESLint installed because it came with `next lint`. We don't
actively use ESLint, it was just used in the playgrounds where we test
things out. Dropping this reduces the amount of dependencies we have to
deal with.

Closes: #19867
Closes: #19852

# Test plan

Tested both the `nextjs` and `v3` playgrounds after bumping the Next.js
dependency and getting rid of ESLint, and they still work as expected.
2026-04-03 21:07:27 +02:00
Jordan Pittman
d7fc281a0e
Use json preprocessor for new-line delimited JSON files (#19862)
## Summary

This specializes the `.jsonl` and `.ndjson` file extensions so they're
preprocessed like JSON instead of by the standard scanner. This prevents
them from creating thousands of sub machines and reduces scanning time
(see #17125 where this was done for `.json` files).

It seems reasonable to handle new-line delimited JSON files as well
otherwise scanning these files can take quite a long time.

It's quite unlikely that these will contain classes so, alternatively,
these *could* go in the binary extensions list so they get ignored
entirely.

## Test plan

I ran manual tests inside the `oxide` crate against some large-ish JSONL
files (5MB–15MB). These changes bring down scanning time from 2s–3s on
my M3 Max (via `cargo test --release …`) to less than 20ms.

I also ran tests through a full CLI build pipeline on a low-spec linux
box. This change brought scanning time down from ~90s to ~300ms for a
single ~15MB file.
2026-03-26 23:42:03 +01:00
Robin Malfait
df6209ab8b
Canonicalize negative arbitrary values (#19858)
This PR adds a few more canonicalizations for some cases I noticed on
our templates.

When dealing with arbitrary values, and the utility is a "negative"
utility, then we will try to put the `-` inside of the arbitrary value:

```diff
- -left-[9rem]
+ left-[-9rem]
```

The idea is that the arbitrary value is already an escape hatch for when
a value is not available by default. The `-` in front uses an implicit
`calc(<expression> * -1)` which might be confusion if you have an value
like this already.

This also can allow for some further optimizations. For example
```diff
- -mt-[492px]
  ↓↓↓↓↓↓↓                           Into a simpler arbitrary value
+ mt-[-492px]
  ↓↓↓↓↓↓↓                           Into a bare value
+ mt-123
```

This PR also improve the constant folding of calc expressions a bit more
such that nested calc expressions with 2 constants and an unknown can be
folded. Bit of a mouthful, but it allows us to handle this:
```diff
- mt-[calc(-1*calc(-1*var(--foo)))]
  ↓↓↓↓↓↓↓                           The -1 * -1 becomes a no-op
+ mt-[var(--foo)]
  ↓↓↓↓↓↓↓                           Into the shorthand for CSS variables
+ mt-(--foo)
```

Now that we can handle moving the `-` into the arbitrary value, there
are also cases where we can get the `-` _out_ of the arbitrary value:
```diff
- mt-[calc(-1*var(--foo))]
  ↓↓↓↓↓↓↓                           Simplify calc, move `-` to the front
+ -mt-[var(--foo)]
  ↓↓↓↓↓↓↓                           Into the shorthand for CSS variables
+ -mt-(--foo)
```

Another missing piece that this PR adds is the concept of canonicalizing
or normalizing calc expressions. This is a separate step used when
calculating the signature for each utility. This allows us to normalize
`calc(-1*var(--foo))` and `calc(var(--foo)*-1)`. Without this they would
not be considered the same, but not it will.

It's only used when comparing values, it won't unify the actual
arbitrary values with this logic (at least for now).

With the additional constant folding logic and the canonicalization when
comparing signatures it unlocks the necessary power to perform the above
transformations.

## Test plan

1. Existing tests still pass
2. Added additional tests for the constant folding logic
3. Added tests for the canonicalization of calc expressions
4. Added new tests where we move the `-` inside the value, or move the
`-` outside of the arbitrary value.
2026-03-26 14:44:09 +01:00
Robin Malfait
52fd421cc9
Small refactor of canonicalization tests (#19851)
This are getting a little bit out of hand here, so this is an initial
refactor.

## Test plan

1. All tests are still there
2. All tests are still passing
2026-03-25 15:18:14 +01:00
Robin Malfait
c385fd36bc
use test.each instead of manual loop 2026-03-25 12:16:50 +01:00
Robin Malfait
0d6e038889
fix index in test name 2026-03-25 12:16:50 +01:00
Robin Malfait
88a2d22c2f
Add more canonicalization rules for deprecated utilities (#19849)
This PR adds more canonicalization rules for deprecated utilities.

| Before | After |
| --- | --- |
| `overflow-ellipsis` | `text-ellipsis` |
| `start-full` | `inset-s-full` |
| `-start-full` | `-inset-s-full` |
| `start-auto` | `inset-s-auto` |
| `start-px` | `inset-s-px` |
| `-start-px` | `-inset-s-px` |
| `start-8` | `inset-s-8` |
| `-start-8` | `-inset-s-8` |
| `start-123` | `inset-s-123` |
| `-start-123` | `-inset-s-123` |
| `end-full` | `inset-e-full` |
| `-end-full` | `-inset-e-full` |
| `end-auto` | `inset-e-auto` |
| `end-px` | `inset-e-px` |
| `-end-px` | `-inset-e-px` |
| `end-8` | `inset-e-8` |
| `-end-8` | `-inset-e-8` |
| `end-123` | `inset-e-123` |
| `-end-123` | `-inset-e-123` |

In a few cases we already had canonicalization rules, for example
`start-8` where `8` is one of the default suggested spacing scale
values. But this now adds support for positive and negative values that
exceed the default suggested spacing scale as well as some keywords.

## Test plan

1. Existing tests pass
2. Added new tests to ensure these canonicalizations work
2026-03-25 11:38:28 +01:00
Robin Malfait
e4856c9720
Make upgrade tooling more stable (#19846)
This PR is an attempt to make the upgrade tooling more stable.


### TL;DR

1. When migrating from Tailwind CSS v3 → Tailwind CSS v4, only migrate
files listed in the `config.content` instead of relying on v4's auto
content detection feature
2. Skip writing files that have not been changed
3. Write changed files in a safe way: first write to a temporary file,
then rename the file atomically
4. Never migrate files that are git ignored, even if they are listed in
the `config.content` file
5. Always ignore `.env` and `.env.*` files when scanning for files. Most
people will have this in their `.gitignore` file, but if not, then this
is a fallback mechanism.

---

Looking at the #18972 issue, it looks like some people are running into
weird situations where some of the contents is just gone.

I have never been able to reproduce this on my own devices and in my own
projects unfortunately. But there is definitely _something_ happening
that's not right that people are running into.

Therefore, this PR is an attempt to fix what I think _might_ be wrong,
but I'm not 100% sure if these fixes are enough, or if something else is
still happening here.

This builds on top of the #19779 PR which has some small fixes, but is
incomplete to make this work.

### What's happening

Looking at some of the comments, it looks like a few things are
happening such as:

1. The upgrade tool is emptying out my files — it looks like these are
only happening if you ctrl+c while the process is taking a while. It
could be that a lot of files are being checked and therefore the tooling
looks like its stuck.
6. The upgrade tool is looking at files it shouldn't look at — in
Tailwind CSS v4 we have this concept of the auto-content detection. This
means that we will look at any plain text file that is not git ignored.

### Fixes


#### Emptying out files

The files being emptied looks like it's because how `fs.writeFile`
behaves by default. It opens the file handle with the `w` flag, which
will first truncate the file before writing the new contents. This is
not a single atomic operation, so a killed process in the middle will
cause invalid state.

When we migrate your template files, everything is happening in promises
to migrate things at the same time. When a lot of files are being
scanned, truncating might have happened already before we write the new
content. Since we migrate a bunch of files in parallel, a ctrl+c could
cause data loss in multiple files.

To mitigate this, I switched to an alternative way of writing files. 

1. First, we do some quick checks where if the contents didn't change we
just bail out immediately. Files that don't include Tailwind CSS classes
won't change, and therefore we don't need to override these files with
the same contents.
2. When the migrated contents is empty, we bail out as well. I'm 100%
sure that this is not the spot where the "emptying out" happens, I still
believe it happens in the `writeFile` itself, but added it just in case.
7. Next, I introduced a safe write, where we first write to a temporary
file in the same folder. We could write it to `/tmp`, but then we can't
guarantee that we are on the same file system.
   
If we ctrl+c at this stage, then the worst case scenario is that you
have additional temporary files in your project, but your original files
are still there.
   
Once that file was written, we will use the atomic `fs.rename`. This
should be atomic as long as we are on the same file system, so either
the rename didn't happen yet, or it completed.

I added an integration test for this, but I had to change the
`writeFile` implementation slightly. In the test, we will truncate the
file first, after that we will write the new contents. This is so that
we have enough time to kill the current process and allows us to verify
that we didn't clear out the file. Again, this is a hacky way of testing
this, just because I can't reproduce this issue myself, let alone
reproduce it reliable in a CI environment.

Note: we are also using `realpath` to make sure that we are updating the
real file. Otherwise, if we were dealing with a symlinked file, we would
override the symlink with a "hard" copy instead.

#### Touching files that should not be touched

During the migration, we rely on the Tailwind CSS v4 auto detection
logic which means that it will scan any plain text file that is not git
ignored. Therefore changes to php files could happen because in theory
they could contain Tailwind CSS classes.

To solve this, when migrating from Tailwind CSS v3 to Tailwind CSS v4,
we will _only_ take the sources into account that were listed in the
`config.content` array. Since this was a requirement in Tailwind CSS v3,
it should be safe to rely on this array.

Additionally, this will make sure that we are dealing with way fewer
files to migrate as well.

On top of that, files that match the patterns in the content array that
are git ignored will also be skipped. This is to prevent that we mutate
files in `node_modules` for example.

In one of the comments I read that `.env` files were emptied out. In
most cases people will have these files gitignored but I explicitly
added `.env` and `.env.*` as files to never ever touch by default when
scanning.

Last but not least, this also updates the output a little bit of the
upgrade tool in case we skip content files (because of git ignore) and
if we changed a file.

<img width="1122" height="1376" alt="image"
src="https://github.com/user-attachments/assets/318fdbbf-e319-4c7e-9648-ee9283842624"
/>

- "Git ignored folder, skipping: `./node_modules`": this is because the
content array looks like this while the `node_modules` are being
ignored:
<img width="1090" height="398" alt="image"
src="https://github.com/user-attachments/assets/7d694720-5671-47ec-bb2e-f24c5f2c4248"
/>

- "Migrated
`./resources/views/vendor/filament-panels/components/logo.blade.php`":
this is because **I** made a change to showcase this feature.

Fixes: #18972
Closes: #19779

### Test plan

1. Existing tests still pass
1. Added a dedicated integration test to ensure that we only take
`config.content` into account when migrating from Tailwind CSS v3 to
Tailwind CSS v4 projects.
1. Added a dedicated integration test to make sure that files listed in
`config.content` that are also git ignored, will still be skipped.
1. Added a dedicated integration test to ensure that when `writeFile` is
cancelled mid-write that our old files are still present.
1. Added a dedicated integration test to ensure that we ignore `.env`
and `.env.*` files even if you didn't git ignore them.

[ci-all] To verify on Windows

---------

Co-authored-by: Sami <sychocouldy@gmail.com>
2026-03-24 17:24:12 +01:00
Robin Malfait
2c1ef9eb25
Use --placeholder-color instead of --background-color for placeholder-* utilities (#19843)
This PR fixes an issue where `placeholder-*` utilities were reading
values from `--background-color` instead of `--placeholder-color`. In
Tailwind CSS v3, we read from `placeholderColor` which is why we should
use `--placeholder-color` here as well.


f38be227df/src/corePlugins.js (L2317)

That said, this is technically a breaking change in case somebody relies
on `--background-color` for `placeholder` values. But since this is text
related, and most people will rely on the default `--color` values
instead, I think it's safe to change this as-is.

In the unlikely event that somebody _does_ rely on this, then we have 2
options:

1. Guide them to make use of `--placeholder-color` instead (preferred
solution)
2. Re-add `--background-color` after the `--placeholder-color` (band-aid
solution, but might be worth it who knows)

Fixes: #19838

## Test plan

1. Existing tests still pass
2. Verified in the Tailwind CSS v3 codebase that we did read from
`placeholderColor` which in turn reads from `color` by default. Which is
equivalent to `--placeholder-color` and `--color` in Tailwind CSS v4.
2026-03-23 15:57:20 +01:00
Robin Malfait
6ec8b89191
update CHANGELOG 2026-03-23 15:35:48 +01:00
Robin Malfait
28d526859d
Collapse more utilities by expanding their declarations (#19842)
This PR adds more declaration expansions such that we can collapse more
utilities.

While testing #19837 I noticed that in my tests some utilities weren't
canonicalized correctly. As part of that PR, we check for
`parsedCandidate.value === null`, which means that a functional utility
without a value is skipped. We do have utilities like that such as
`border` (which is equivalent to `border-1`). But while testing, I
noticed that `border-x border-y` should collapse to `border` but they
didn't. This PR fixes that.

By expanding these properties to their long-form physical properties
(instead of the shorter logical properties) we make the signatures of
utilities a bit bigger, but also more correct such that we can collapse
the physical form into logical utilities.

To make this more concrete, this PR allows for the following
canonicalizations now:

| Input | Output |
| --- | --- |
| `border-t-123 border-r-123 border-b-123 border-l-123` | `border-123` |
| `border-t-1 border-r-1 border-b-1 border-l-1` | `border` |
| `border-t-123 border-b-123` | `border-y-123` |
| `border-l-123 border-r-123` | `border-x-123` |
| `border-t-red-500 border-r-red-500 border-b-red-500 border-l-red-500`
| `border-red-500` |
| `border-t-red-500 border-b-red-500` | `border-y-red-500` |
| `border-l-red-500 border-r-red-500` | `border-x-red-500` |
| `scroll-mt-123 scroll-mr-123 scroll-mb-123 scroll-ml-123` |
`scroll-m-123` |
| `scroll-mt-123 scroll-mb-123` | `scroll-my-123` |
| `scroll-ml-123 scroll-mr-123` | `scroll-mx-123` |
| `scroll-pt-123 scroll-pr-123 scroll-pb-123 scroll-pl-123` |
`scroll-p-123` |
| `scroll-pt-123 scroll-pb-123` | `scroll-py-123` |
| `scroll-pl-123 scroll-pr-123` | `scroll-px-123` |
| `overflow-x-hidden overflow-y-hidden` | `overflow-hidden` |
| `overscroll-x-contain overscroll-y-contain` | `overscroll-contain` |

## Test plan

1. Existing tests pass
2. Added a few more tests to verify that these canonicalizations work
2026-03-23 15:33:44 +01:00
Aaron Tinio
b55d96002c
fix(canonicalize): collapse arbitrary values into shorthand utilities (#19837)
The guard on `dynamicUtilities` restricted root-swapping to named
values, so arbitrary values like `px-[1.2rem] py-[1.2rem]` were never
collapsed into `p-[1.2rem]`.

This is what caused #19835 — in `--stream` mode, the collapse only
happened if an earlier line caused the shorthand to be registered in
`STATIC_UTILITIES_KEY` as a side effect, making the output
non-deterministic. The underlying issue is that arbitrary value collapse
wasn't supported at all.

The fix relaxes the guard from `parsedCandidate.value?.kind !== 'named'`
to `parsedCandidate.value === null`. `cloneCandidate` and
`printCandidate` already handle arbitrary values, so the root-swapping
machinery works without other changes.

The iteration in `dynamicUtilities` is over
`designSystem.utilities.keys('functional')` — a fixed set of roots, not
input-proportional — so the performance cost of including arbitrary
values should be negligible.

Fixes #19835.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-03-23 11:32:25 +01:00
Qingyu Wang
17d324f896
Fix webpack loader cache key for resource queries (#19723)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

`@tailwindcss/webpack` currently uses `this.resourcePath` as the cache
key, which ignores the resource query. When the same CSS file is
imported multiple times with different `resourceQuery` values, all of
those imports share a single `CacheEntry`. That means the utilities
discovered for one entry can leak into the CSS output for another entry.

This PR changes the cache key to use `this.resource` (path + query)
instead, while still using `this.resourcePath` for all filesystem work.

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

- `pnpm test:integrations -- webpack/loader.test.ts`  
  - Confirms all existing webpack loader integration tests pass.  
- Confirms the new `@tailwindcss/webpack loader isolates cache by
resource including query` test passes, verifying that two entries
importing the same CSS file with different queries produce isolated
outputs (`dist/a.css` only contains `only-a` / `--color-red-500`, and
`dist/b.css` only contains `only-b` / `--color-blue-500`).

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-03-20 16:34:11 +00:00
Amir Zarrinkafsh
5cb1efdf41
fix(vite): resolve tsconfig paths in CSS and JS resolvers (#19803)
Change `aliasOnly` from `true` to `false` when calling Vite's resolver
so that the full resolution pipeline runs, including the oxc resolver
responsible for tsconfig path resolution.

When `aliasOnly` was `true`, only the @rollup/plugin-alias plugin ran,
which meant `resolve.tsconfigPaths: true` had no effect on CSS `@import`
or JS `@plugin` resolution in `@tailwindcss/vite`.

Closes #19802.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-03-20 15:19:18 +00:00
Robin Malfait
bd30a716e6
Fix crash due to invalid characters in candidate (#19829)
This PR fixes an issue where the compiler can crash if it encounters an
invalid codepoint.

When we extract potential candidates from files, it could be that we
encounter values that look like a class or a CSS variable, if it turns
out that it's an invalid CSS variable we can ignore it.

The problem is that sometimes there are escaped values in there that
result in invalid code points crashing the compiler.

This PR fixes that by gracefully handling that and making sure that
invalid code points are replaced by `\uFFFD` as per the spec.

The bug report
(https://github.com/tailwindlabs/tailwindcss/issues/19786) has a clean
example where a piece of text looks like a CSS variable, but contains
invalid code points.

```
--Coding-Projects-CharacterMapper-Master-Workspace\d8819554-4725-4235-9d22-2d0ed572e924
```

Luckily we can fix this today by ignoring the file paths that contain
these strings using `@source not "…";`, but the better way is to
actually fix this.

To solve this, instead of blindly passing numbers to
`String.fromCodePoint`, we will first validate whether it's a valid
codepoint:

1. `0x0000` — `0x10FFFF` (inclusive) is the range of valid code points.
See: https://infra.spec.whatwg.org/#code-point
2. `0xD800` — `0xDBFF` (inclusive) are leading surrogates. See:
https://infra.spec.whatwg.org/#leading-surrogate
3. `0xDC00` — `0xDFFF` (inclusive) are trailing surrogates. See:
https://infra.spec.whatwg.org/#trailing-surrogate

In the code we use the `0xD800` — `0xDFFF` range because the ranges
overlap.

There are various references in the spec to replace surrogates (and
invalid codepoints) with `\uFFFD`. Here is one of them:
https://drafts.csswg.org/css-syntax-3/#consume-escaped-code-point

Fixes: https://github.com/tailwindlabs/tailwindcss/issues/19786
Fixes: #19801 (this issue talks about a similar invalid code point
issue)

## Test plan

1. Added a regression test where the above string was used as a CSS
variable
2. Added a regression test for the unescape functionality to make sure
that invalid code points and surrogates are replaced by the `\uFFFD`
replacement character.

[ci-all] Just to verify on Windows as well
2026-03-20 14:51:37 +01:00
Robin Malfait
7482d47a54
Add canonicalizations for tracking-* utilities (#19827)
This PR adds support for canonicalizations for `tracking-*` utilities.

This one is a bit of a funny one, if you take a look at the linked
issue, there is a beautiful table:

| Utility Name | Value | Arbitrary Value | Throws Suggestion |
| - | -: | - | - |
| tracking-tighter | -0.05em | tracking-[-0.05em] | ✗ |
| tracking-tight | -0.025em | tracking-[-0.025em] | ✗ |
| tracking-normal | 0em | tracking-[0em] | ✗ |
| tracking-wide | 0.025em | tracking-[0.025em] | ✗ |
| tracking-wider | 0.05em | tracking-[0.05em] | ✗ |
| tracking-widest | 0.1em | tracking-[0.1em] | ✓ |

It doesn't really make sense to _why_ only the `tracking-widest` one is
properly suggested here. Until you look a little bit closer.

Turns out that `-tracking-tighter` is equivalent to `tracking-wider`,
`-tracking-tight` is equivalent to `tracking-wide` and so on.

The way the canonicalization works internally is by generating a
signature for a given utility class. If two utilities have the exact
same signature, we can consider them the same. In this case
`tracking-widest` and `tracking-[0.1em]` have the same signature.

One of the rules we have internally is that if we find more than one
replacement utility then we don't really know what to do, so we bail.
Because if you get `foo` or `bar`, which one do you pick?

If we refer to this above table again, the moment we want to
canonicalize the `tracking-[-0.05em]` we get two suggestions:
`tracking-tighter` and `-tracking-wider`, since we don't know what to
do, we bail and we don't suggest anything.

So the reason that `tracking-widest` _was_ suggested is just because we
don't have a `-tracking-tightest`.

How do we fix this? Well, since we have `tracking-*` and `-tracking-*`
utilities, I wanted to deprecate the `-tracking-*` ones for named
utilities (where the values come from your theme) because that doesn't
really make sense.

However, we have this exact pattern documented here:
https://tailwindcss.com/docs/letter-spacing#using-negative-values Which
means that I can't just deprecate those utilities.

<img width="723" height="511" alt="image"
src="https://github.com/user-attachments/assets/164b659b-abe9-4f6e-a176-701dd7ea505a"
/>

Instead, I added a different rule which says that if you get multiple
possible replacements, then we prefer the "positive" one, the one
without the `-`. Also added some additional checks to make sure that if
you get `foo`, `-bar`, `baz`, that we also bail because we know that we
should prefer `foo` or `baz` over `-bar`, but we don't know if we should
pick `foo` or `baz`...

This additional rule does solve the original issue, and we already
prefer possible values over negative values in other places (related to
bare values).

Fixes:
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1558

## Test plan

1. Existing tests pass
2. Added regression tests to make sure that the table from above _does_
get canonicalized correctly into the expected values.
2026-03-19 19:53:16 +01:00
Robin Malfait
d596b0c43d
4.2.2 (#19821) 2026-03-18 11:44:08 -04:00
Robin Malfait
2228a57a9e
Bump Lightning CSS (#19771)
This PR bumps Lightning CSS. The `node/index.js` file in the package
changed, so we had to update the patched version of it as well.

We also had to update the patches for `@parcel/watcher` and
`lightningcss` now that they ship with `detect-libc@2`. Unfortunately,
we still require the patches because we also need to take the
`process.env.PLATFORM_LIBC` into account. Another issue is that bun
needs to be able to statically analyze the `require(…)` calls, so using
the nice `parts.push` approach doesn't really work here.

# Test plan

1. Existing tests should pass
2. Updated some tests to reflect the changes in the generated CSS based
on the version bump.

[ci-all]
2026-03-18 15:57:52 +01:00
Robin Malfait
f302fce815
Fix canonicalization resulting in empty list (#19812)
This PR fixes a bug in the canonicalization process where if a few
utilities collapse into a smaller one, and the smaller one is part of
the original list, then it results in an empty list.

It will be more clear with an example. Let's say you have this setup:
```
w-[calc(1rem+0.25rem)] h-[calc(1rem+0.25rem)] size-5
```

The first step is that this will result in:
```
w-5 h-5 size-5
```

Then the `w-5 h-5` can turn into `size-5`. But the existing `size-5`,
can also be replaced by the `size-5`.

Internally, when we have a replacement, then we mark all the classes
that can be replaced as "droppable", so they would be dropped from the
list. But in this scenario we also marked `size-5` as droppable,
resulting in an empty list.

If an additional class existed:
```
w-[calc(1rem+0.25rem)] h-[calc(1rem+0.25rem)] size-5 flex
```

The result would be 
```
flex
```

Instead of the expected:
```
size-5 flex
```


## Test plan

1. Existing tests pass
2. Added new tests with and without an additional class
2026-03-17 13:00:31 +01:00
Robin Malfait
bb2f170514
Improve canonicalization for bare values exceeding default spacing scale suggestions (#19809)
This PR adds support for canonicalization of utilities that accept bare
values and exceed the default spacing scale we use for intellisense.

Right now, all utilities are behind functions, so the only way to know
whether something compiles is by compiling a candidate, e.g. `w-8` and
passing it to the utility functions. To help us, we use the intellisense
APIs that we use for suggestions.

Most utilities that accept bare values, have suggestions up until
`*-96`, so `w-96 h-96` would be canonicalized to `size-96`. But the
moment we exceed that, the result stays as-is.
```
→ w-96 h-96
= size-96

→ w-1234 h-1234
= h-1234 w-1234
```

This PR ensures that the last scenario also gets canonicalized to
`size-1234` instead of staying as `h-1234 w-1234`.

```
→ w-96 h-96
= size-96

→ w-1234 h-1234
= size-1234
```

## Test plan

1. Existing tests pass
2. Added new tests for utilities with bare values

[ci-all] just to see if this additional logic doesn't cause timeouts in
CI for WIndows. In my testing this doesn't have a significant impact on
performance at all.
2026-03-16 22:44:29 +01:00
Aaron Tinio
aaaefe8b5d
Add --stream flag to canonicalize subcommand (#19796)
## Summary

- Adds `--stream` flag to `tailwindcss canonicalize` that reads
candidate groups from stdin line by line and writes canonicalized
results to stdout
- Keeps the design system loaded across requests, making it suitable as
a long-running sidecar process
- Empty lines pass through, keeping request/response pairs aligned

## Motivation

Non-JS tools (formatters, editor plugins, etc.) currently have no
lightweight way to canonicalize Tailwind classes. The existing batch
mode works for one-off use, but tools that need to canonicalize
repeatedly pay the cost of loading the design system each time.

With `--stream`, a tool can start `tailwindcss canonicalize --stream`
once and send candidate groups over stdin as needed:

```sh
$ echo -e "py-3 p-1 px-3\nmt-2 mr-2 mb-2 ml-2" | tailwindcss canonicalize --stream
p-3
m-2
```

Related discussion:
https://github.com/tailwindlabs/tailwindcss/discussions/19736

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-03-16 15:00:11 +00:00
SidWuChou
d24b1127bc
fix: remove unused braces dependencies (#19797)
Removes unused braces and @types/braces from @tailwindcss/upgrade
package.json as reported in issue #19794

---------

Co-authored-by: CG1AI <sidwu@CG1AIdeMac-mini.local>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-03-15 21:10:54 +00:00
Robin Malfait
faa5e8849b
Cleanup inconsistencies related to (regex) escapes (#19804)
This PR does some generic cleanup to the codebase.

I'm playing with oxfmt and oxlint and noticed some unnecessary escapes.
Might add these dependencies to the project later (and rolldown for
building). But baby steps for now.

## Test plan

1. All tests should still pass
2026-03-15 22:04:58 +01:00
Robin Malfait
a4be983865
increase timeout of canonicalization tests 2026-03-12 21:08:06 +01:00
Robin Malfait
59b0329f85
Add support for Vite 8 in @tailwindcss/vite (#19790)
This PR adds support for Vite 8 when using the `@tailwindcss/vite`
package.

From the package's perspective, not a lot had to change, just the `vite`
peer dependency now has an additional `^8.0.0` version range.

Closes: #19789

## Test plan

1. Existing tests pass
2. Manually tested in the `./playgrounds/vite` playground
3. Added Vite 8 integration tests to verify that the plugin works with
Vite 8
2026-03-12 20:54:34 +01:00
Robin Malfait
dfc449c9ff
slightly re-word CHANGELOG 2026-03-12 19:50:16 +01:00
Hiroshi Ogawa
bf441a799f
fix(vite): skip full reload for server only modules scanned by client css (#19745)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

- Closes https://github.com/tailwindlabs/tailwindcss/issues/19744
- Closes https://github.com/vitejs/vite-plugin-react/issues/1118
- Closes https://github.com/wakujs/waku/issues/1963

The change in https://github.com/tailwindlabs/tailwindcss/pull/19670
didn't take account for server only modules managed by SSR framework.
Forcing full reload for this path breaks server HMR. This PR added a
check to determine whether the same modified file has associated modules
in a different environment module graph to avoid this.

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

Added an integration test for React router HDR (server loader hmr). This
test fails on main.

Also the local build is tested on `@vitejs/plugin-rsc` CI and confirmed
the fix https://github.com/vitejs/vite-plugin-react/pull/1132

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-03-12 18:49:41 +00:00
Robin Malfait
6b54dd8630
Fix internal source map warnings (#19788)
This PR fixes an internal source map warning when running tests. To
solve this, we encode the `=` as `\x3d` instead. I'm not 100% sure if
Vitest was hanging on this but it solved the following warning:

```
 ✓  @tailwindcss/cli  src/utils/format-ns.test.ts (21 tests) 3ms
11:43:59 AM [vite] (ssr) Failed to load source map for /Users/robin/github.com/tailwindlabs/tailwindcss/packages/@tailwindcss-node/dist/index.mjs.
Error: An error occurred while trying to read the map file at ${i}
Error: ENOENT: no such file or directory, open '/Users/robin/github.com/tailwindlabs/tailwindcss/packages/@tailwindcss-node/dist/${i}'
    at open (node:internal/fs/promises:634:25)
    at Object.readFile (node:internal/fs/promises:1238:14)
    at extractSourcemapFromFile (file:///Users/robin/github.com/tailwindlabs/tailwindcss/node_modules/.pnpm/vite@7.0.0_@types+node@20.19.1_jiti@2.6.1_lightningcss@1.31.1_patch_hash=tzyxy3asfxcqc7ihroou_7epcep7uhfc7zidzdmxzgvdzwi/node_modules/vite/dist/node/chunks/dep-Bsx9IwL8.js:8349:65)
    at loadAndTransform (file:///Users/robin/github.com/tailwindlabs/tailwindcss/node_modules/.pnpm/vite@7.0.0_@types+node@20.19.1_jiti@2.6.1_lightningcss@1.31.1_patch_hash=tzyxy3asfxcqc7ihroou_7epcep7uhfc7zidzdmxzgvdzwi/node_modules/vite/dist/node/chunks/dep-Bsx9IwL8.js:26405:22)
```

## Test plan

1. Existing tests pass
2. Added a failing test to ensure that the source maps are emitted
correctly
2026-03-12 12:21:00 +01:00
Robin Malfait
ad9fdef005
drop unnecessary test 2026-03-11 20:35:35 +01:00
Robin Malfait
e96909accd
Add tailwindcss canonicalize sub-command (#19783)
This PR adds a new `tailwindcss canonicalize` sub-command.
2026-03-11 20:30:21 +01:00
Robin Malfait
d5717f2307
run prettier 2026-03-11 19:59:38 +01:00
Kirk Ouimet
51aa9d799c
fix(canonicalize): handle utilities with empty property maps in collapse (#19727)
## Problem

`canonicalizeCandidates` crashes when called with `collapse: true` and
the candidate list includes utilities whose CSS output contains no
standard declaration properties (only `@property` rules and CSS custom
properties).

This is reproducible with vanilla Tailwind CSS and no custom
configuration:

```js
designSystem.canonicalizeCandidates(['shadow-sm', 'border'], { collapse: true })
// TypeError: X is not iterable
```

```js
designSystem.canonicalizeCandidates(['shadow-sm', 'border'], { collapse: true })
// TypeError: Cannot read properties of null (reading 'has')
```

All shadow utilities (`shadow-sm`, `shadow-md`, `shadow-lg`,
`shadow-xl`) crash when combined with any other utility and `collapse:
true`.

This was discovered via `eslint-plugin-better-tailwindcss`, which calls
`canonicalizeCandidates` with `collapse: true` for its
`enforce-canonical-classes` rule. The crash brings down ESLint entirely.

## Root cause

In `collapseGroup`, the `otherUtilities` array is built by mapping over
each candidate's property values:

```ts
let otherUtilities = candidatePropertiesValues.map((propertyValues) => {
  let result: Set<string> | null = null
  for (let property of propertyValues.keys()) {
    // ... builds result ...
  }
  return result! // returns null if propertyValues has no keys
})
```

When a utility like `shadow-sm` generates CSS with `@property` rules and
custom property declarations but no standard CSS properties,
`propertyValues.keys()` is empty, the loop never executes, and `result`
stays `null`. The non-null assertion `result!` returns `null` into the
array.

Downstream code then crashes when iterating or calling `.has()` on the
null entry:

```ts
for (let i = 0; i < otherUtilities.length; i++) {
  let current = otherUtilities[i]     // null
  for (let property of current) {     // "X is not iterable"
    if (other.has(property)) {        // "Cannot read properties of null"
```

## Fix

Return an empty `Set` instead of `null` when a utility has no property
keys:

```ts
return result ?? new Set<string>()
```

This is semantically correct: a utility with no standard properties
cannot be linked to or collapsed with any other utility, which is
exactly what an empty Set represents in the linking algorithm. It won't
cause false collapses or suppress valid collapses of other utilities.

## Test plan

- Added test: `collapse does not crash when utilities with no standard
properties are present`
- Verifies `shadow-sm + border`, `shadow-md + p-4`, and `shadow-sm +
shadow-md` don't throw
- Verifies the candidates are returned uncollapsed (correct behavior)
- All 1218 existing tests continue to pass

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-03-10 16:48:07 +01:00
Robin Malfait
c586bd6a94
Canonicalize calc(var(--spacing)*…) expressions into --spacing(…) (#19769)
This PR canonicalizes usages of `calc(var(--spacing)*…)` to
`--spacing(…)` when used in arbitrary values.

Some examples:

| Before | After |
| --- | --- |
| `pt-[min(20%,calc(var(--spacing)*8))]` | `pt-[min(20%,--spacing(8))]`
|
| `pt-[min(20%,calc(var(--spacing)*var(--other)))]` |
`pt-[min(20%,--spacing(var(--other)))]` |
| `pt-[calc(var(--spacing)*8)]` | `pt-8` |
| `pt-[calc(var(--spacing)*var(--other))]` |
`pt-[--spacing(var(--other))]` |
| `[padding-top:min(20%,calc(var(--spacing)*8))]` |
`pt-[min(20%,--spacing(8))]` |
| `[padding-top:min(20%,calc(var(--spacing)*var(--other)))]` |
`pt-[min(20%,--spacing(var(--other)))]` |
| `[padding-top:calc(var(--spacing)*8)]` | `pt-8` |
| `[padding-top:calc(var(--spacing)*var(--other))]` |
`pt-[--spacing(var(--other))]` |


## Test plan

1. Existing tests pass
2. Added new tests
2026-03-08 20:19:35 +01:00
Robin Malfait
bf2e2fe08a
Extract classes from interpolated expressions in Ruby (#19730)
This PR ensures that interpolated expressions in Ruby syntax are
correctly extracted.

The issue was that we ignore comments in Ruby syntax (which start with
`#`). We already made an exception for locals (`<%# locals: … %>`), but
we also need to handle interpolated expressions (`#{ … }`) in the same
way because they are not comments.

Fixes: #19728

## Test plan

1. Existing tests pass
2. Added a regression test for this scenario
3. Tested using the extractor on the given code snippet:

<img width="1461" height="1856" alt="image"
src="https://github.com/user-attachments/assets/b48f7042-ca9d-4133-87ae-4c37c633c073"
/>

Notice that the `w-100` gets extracted now.
2026-02-26 12:23:50 +01:00
Adam Wathan
9ded4a23de
Guard object lookups against inherited prototype properties (#19725)
When user-controlled candidate values like "constructor" are used as
keys to look up values in plain objects (staticValues, plugin values,
modifiers, config), they can match inherited Object.prototype properties
instead of returning undefined. This caused crashes like "V.map is not
a function" when scanning source files containing strings like
"row-constructor".

Use Object.hasOwn() checks before all user-keyed object lookups in:
- utilities.ts (staticValues lookup)
- plugin-api.ts (values, modifiers, and variant values lookups)
- plugin-functions.ts (get() config traversal function)

Fixes #19721

https://claude.ai/code/session_011CYSGw3DLh2Z8xnuyoaCgC

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-02-25 15:16:09 +00:00
Robin Malfait
097f982d7a
update changelog 2026-02-23 13:46:49 +01:00
Robin Malfait
1dce64ee7e
4.2.1 (#19714) 2026-02-23 11:45:12 +01:00
Robin Malfait
58d1fe3948
Fix missing extracted classes in mdx files (#19711) 2026-02-22 13:02:05 +01:00
Robin Malfait
1a973557ed
update webpack readme to be a bit more consistent with other readmes 2026-02-20 12:04:41 +01:00
Robin Malfait
14083ad9aa
use simpler tailwindcss import 2026-02-20 12:03:25 +01:00
Kirk Ouimet
d15d92ca60
Allow trailing dash in functional utility names (#19696)
## Problem

Tailwind 4.2.0 introduced stricter `@utility` name validation (#19524)
that rejects functional utility names where the root ends with a dash
after stripping the `-*` suffix. This breaks a valid and useful naming
pattern where a double dash separates the CSS property from a value
scale:

```css
@utility border--* {
  border-color: --value(--color-border-*, [color]);
}
```

This produces: `border--0`, `border--1`, `border--2`, etc.

The error message is:

> `@utility border--*` defines an invalid utility name. Utilities should
be alphanumeric and start with a lowercase letter.

## Why this pattern matters

The double-dash convention creates a clear visual grammar in class
names. The first segment names the CSS property, and the double dash
separates it from the semantic scale value. In a dense className string
like `border border--0 background--0 content--4`, the scale values (0,
0, 4) are immediately scannable, distinct from the single-dash property
names around them.

This pattern is actively used in production design systems for semantic
color scales (background, content, border, shadow) with values from
0-10.

## Why the restriction is unnecessary

The validation comment states the concern is that `border--*` could
match the bare class `border-` when using default values. However, this
edge case is already handled:

1. **`findRoots` in `candidate.ts`** (line 887) already rejects empty
values: `if (root[1] === '') break`
2. **The Oxide scanner** already extracts double-dash candidates
correctly, as confirmed by existing tests: `("items--center",
vec!["items--center"])`

The candidate parser and scanner both handle this case. The validation
was an overcorrection.

## Changes

- Removed the trailing-dash check from `isValidFunctionalUtilityName` in
`utilities.ts`
- Updated the existing unit test from `['foo--*', false]` to `['foo--*',
true]`
- Added an integration test proving `@utility border--*` compiles
correctly with theme values

## Test results

All 4121 tests pass across the tailwindcss package, including the new
integration test.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-02-19 14:40:37 +00:00
ginnyTheCat
7a54d15f72
Correct CHANGELOG to mention inset not inline (#19700)
I noticed this when reading the changelog to migrate a project to the
new version. I'm not sure whether retroactively changing the changelog
is considered acceptable behaviour in this project or whether appending
a notice in the next changelog would be better.

Funnily this was actually guessed in #19613, though hard to notice among
the other many false guesses.
2026-02-19 14:05:37 +01:00
Robin Malfait
1b16411919
4.2.0 (#19695)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2026-02-18 15:45:06 +01:00
Pádraic Slattery
198af9c3ae
chore: Update outdated GitHub Actions versions (#19577)
## Summary  
This PR updates outdated GitHub Action versions by migrating
`actions/download-artifact` from `v6` to `v7` in the following
workflows:
- `.github/workflows/prepare-release.yml`  
- `.github/workflows/release.yml`  
- `.github/workflows/release-insiders.yml`  

## Test plan  
The changes will be tested in the CI pipeline of the pull request.  

[×] Summary  
[×] Test plan
2026-02-18 12:27:13 +00:00
Pavan Shinde
6118f4f6a7
Fix/misc docs and tests (#19652)
## Summary
This PR makes a few small consistency fixes:

- Fix outdated GitHub links that still point to the old
`tailwindcss/tailwindcss` org and update them to
`tailwindlabs/tailwindcss` (README + contributing docs + PR template).
- Fix punctuation in the contributing guide (“i.e., …”).
- Update two test titles to use “cannot” instead of “can not” for
consistency.

No behavior changes.

## Test plan
- Link check (manual): clicked the updated GitHub links in `README.md`,
`.github/CONTRIBUTING.md`, and `.github/PULL_REQUEST_TEMPLATE.md` to
confirm they resolve correctly.
- (Optional) `pnpm test` — not required since changes are
docs/test-title-only.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-02-18 12:23:48 +00:00
Cameron
5a4a7eba3a
fix(canonicalize): prevent collapse cache pollution across calls (#19675)
## Summary

fixes
https://github.com/schoero/eslint-plugin-better-tailwindcss/issues/321

This PR fixes an order-sensitive canonicalization bug. This bug caused
issues when running eslint-plugin-better-tailwindcss as the order in
which files are linted in is not consistent. This caused, in some
scenarios, `canonicalizeCandidates(..., { collapse: true,
logicalToPhysical: true, rem: 16 })` to stop collapsing valid
combinations (for example `h-4 + w-4 -> size-4`) after unrelated prior
calls.

To reproduce this issue:
```
# checkout this branch
$ git checkout c/fix-canonicalizeCandidates
# Revert the fix to the current `main` branch
$ git checkout main ./packages/tailwindcss/src/canonicalize-candidates.ts
# Run the tests
$ pnpm run test
```

This should produce a failure like so:

```

 FAIL   tailwindcss  src/canonicalize-candidates.test.ts > regressions > collapse canonicalization is not affected by previous calls
AssertionError: expected [ 'underline', 'h-4', 'w-4' ] to deeply equal [ 'underline', 'size-4' ]

- Expected
+ Received

  [
    "underline",
-   "size-4",
+   "h-4",
+   "w-4",
  ]

 ❯ src/canonicalize-candidates.test.ts:1167:66
    1165|     designSystem.canonicalizeCandidates(['underline', 'mb-4'], options)
    1166| 
    1167|     expect(designSystem.canonicalizeCandidates(target, options)).toEqual(['underline', 'size-4'])
       |                                                                  ^
    1168|   })
    1169| })
```

```
# reset all changes on this branch
git reset --hard
# run the tests again (they should now pass)
pnpm run test
```

The cause of this bug is that the canonicalization caches used
`DefaultMap` in places where lookups were expected to be read-only.
`DefaultMap.get` inserts missing entried, which mutated shared cache
state during intermediate lookups and made later canonicalization
results depend on prior calls.

By replacing the use of `DefaultMap` with a plain `Map`, it avoids
inserting into the map on lookup paths. I've polyfilled
[`Map#getOrInsert`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/getOrInsert)
as it is not widely available yet, and used that where appropriate.


## Test plan

I wrote a test that fails on `main` branch, I then fixed the issue, and
validated that the test now passes.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-02-18 12:16:59 +00:00
depfu[bot]
d520e1f571
Update bun 1.3.7 → 1.3.9 (patch) (#19678)
<!--depfu-start-->
> 👉 **This PR is queued up to get rebased by Depfu**
<!--depfu-end-->





Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ bun (1.3.7 → 1.3.9) · [Repo](https://github.com/oven-sh/bun)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-02-18 12:00:11 +01:00
depfu[bot]
acf8dd0bf0
Update @types/bun 1.3.7 → 1.3.9 (patch) (#19689)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/bun (1.3.7 → 1.3.9) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-02-18 11:59:30 +01:00
depfu[bot]
d9c4cd8c7c
Update enhanced-resolve 5.18.4 → 5.19.0 (minor) (#19658)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ enhanced-resolve (5.18.4 → 5.19.0) ·
[Repo](https://github.com/webpack/enhanced-resolve)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/webpack/enhanced-resolve/releases/tag/v5.19.0">5.19.0</a></h4>

<blockquote><h3 dir="auto">Features</h3>
<ul dir="auto">
<li>Added TsconfigPathsPlugin (replacement for <code
class="notranslate">tsconfig-paths-webpack-plugin</code>) .</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="e8a6ac9fdd...24ba233554">See
the full diff on Github</a>. The new version differs by 4 commits:</p>
<ul>
<li><a
href="24ba233554"><code>chore(release):
5.19.0</code></a></li>
<li><a
href="10dc3bc8b8"><code>chore:
update eslint config webpack (#471)</code></a></li>
<li><a
href="0d6c6c0fd9"><code>feat:
added TsconfigPathsPlugin (replacement for
tsconfig-paths-webpack-plugin) (#463)</code></a></li>
<li><a
href="958aa89219"><code>chore:
migrate to npm from yarn (#469)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-02-18 11:59:11 +01:00
depfu[bot]
fb42a3ff9e
Update semver 7.7.3 → 7.7.4 (patch) (#19667)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ semver (7.7.3 → 7.7.4) ·
[Repo](https://github.com/npm/node-semver) ·
[Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/npm/node-semver/releases/tag/v7.7.4">7.7.4</a></h4>

<blockquote><h2 dir="auto">
<a
href="https://bounce.depfu.com/github.com/npm/node-semver/compare/v7.7.3...v7.7.4">7.7.4</a>
(2026-01-16)</h2>
<h3 dir="auto">Bug Fixes</h3>
<ul dir="auto">
<li>
<a
href="a29faa5f33"><code
class="notranslate">a29faa5</code></a> <a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/835">#835</a>
cli: pass options to semver.valid() for loose version validation (<a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/835">#835</a>)
(<a
href="https://bounce.depfu.com/github.com/mldangelo">@mldangelo</a>)</li>
</ul>
<h3 dir="auto">Documentation</h3>
<ul dir="auto">
<li>
<a
href="1d28d5e82d"><code
class="notranslate">1d28d5e</code></a> <a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/836">#836</a>
fix typos and update -n CLI option documentation (<a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/836">#836</a>)
(<a
href="https://bounce.depfu.com/github.com/mldangelo">@mldangelo</a>)</li>
</ul>
<h3 dir="auto">Dependencies</h3>
<ul dir="auto">
<li>
<a
href="120968b767"><code
class="notranslate">120968b</code></a> <a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/840">#840</a>
<code class="notranslate">@npmcli/template-oss@4.29.0</code> (<a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/840">#840</a>)</li>
</ul>
<h3 dir="auto">Chores</h3>
<ul dir="auto">
<li>
<a
href="44d7130c60"><code
class="notranslate">44d7130</code></a> <a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/824">#824</a>
bump @npmcli/eslint-config from 5.1.0 to 6.0.0 (<a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/824">#824</a>)
(<a
href="https://bounce.depfu.com/github.com/dependabot">@dependabot</a>[bot])</li>
<li>
<a
href="70735767b6"><code
class="notranslate">7073576</code></a> <a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/820">#820</a>
reorder parameters in invalid-versions.js test (<a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/820">#820</a>)
(<a href="https://bounce.depfu.com/github.com/reggi">@reggi</a>)</li>
<li>
<a
href="5816d4cfd6"><code
class="notranslate">5816d4c</code></a> <a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/829">#829</a>
bump @npmcli/template-oss from 4.28.0 to 4.28.1 (<a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/829">#829</a>)
(<a
href="https://bounce.depfu.com/github.com/dependabot">@dependabot</a>[bot],
<a
href="https://bounce.depfu.com/github.com/npm-cli-bot">@npm-cli-bot</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="a25789b09b...5993c2e42b">See
the full diff on Github</a>. The new version differs by 10 commits:</p>
<ul>
<li><a
href="5993c2e42b"><code>chore:
release 7.7.4 (#839)</code></a></li>
<li><a
href="120968b767"><code>deps:
@npmcli/template-oss@4.29.0 (#840)</code></a></li>
<li><a
href="a29faa5f33"><code>fix(cli):
pass options to semver.valid() for loose version validation
(#835)</code></a></li>
<li><a
href="1d28d5e82d"><code>docs:
fix typos and update -n CLI option documentation (#836)</code></a></li>
<li><a
href="5816d4cfd6"><code>chore:
bump @npmcli/template-oss from 4.28.0 to 4.28.1 (#829)</code></a></li>
<li><a
href="ab9e28a582"><code>chore:
bump @npmcli/template-oss from 4.27.1 to 4.28.0 (#827)</code></a></li>
<li><a
href="44d7130c60"><code>chore:
bump @npmcli/eslint-config from 5.1.0 to 6.0.0 (#824)</code></a></li>
<li><a
href="70735767b6"><code>chore:
reorder parameters in invalid-versions.js test (#820)</code></a></li>
<li><a
href="16a35f5df4"><code>chore:
bump @npmcli/template-oss from 4.26.0 to 4.27.1 (#823)</code></a></li>
<li><a
href="3a3459da45"><code>chore:
bump @npmcli/template-oss from 4.25.1 to 4.26.0 (#818)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-02-18 11:58:49 +01:00
depfu[bot]
06b6473fcc
Update @types/react 19.2.10 → 19.2.14 (patch) (#19681)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react (19.2.10 → 19.2.14) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-02-18 11:58:33 +01:00
Adam Wathan
d0a5612872
Add mauve, olive, mist, and taupe color palettes (#19627)
## Summary

This PR adds four new neutral color palettes to Tailwind CSS: `mauve`,
`olive`, `mist`, and `taupe`. Each palette includes 11 shades (50, 100,
200, 300, 400, 500, 600, 700, 800, 900, 950) defined using the OKLch
color space for perceptually uniform color transitions.

These new palettes expand the available neutral color options beyond the
existing stone, slate, gray, zinc, and neutral palettes, providing
designers with more nuanced choices for different design systems and
brand aesthetics.

The changes include:
- Added color definitions to `packages/tailwindcss/src/compat/colors.ts`
- Added corresponding CSS custom properties to
`packages/tailwindcss/theme.css`

## Test plan

To verify these changes:
1. Build the project and ensure no compilation errors occur
2. Verify that the new color palettes are available in the Tailwind
config
3. Test that utilities like `bg-mauve-500`, `text-olive-600`,
`border-mist-300`, and `ring-taupe-400` work correctly
4. Confirm that the colors render with proper OKLch values in the
browser

https://claude.ai/code/session_01QhC1ZYkW3pCd55WbbqYBNV

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-02-18 11:52:35 +01:00
Pavan Shinde
d9fff9f595
docs: update package README CI badge to main (#19692)
## Summary
Update the GitHub Actions CI status badge URLs in `packages/*/README.md`
to track `branch=main` instead of `branch=next`. This matches the root
README, keeps the badges consistent across the repo, and ensures the
displayed CI status reflects the default branch.

## Test plan
Docs-only change.
Verified the updated badge URLs in the modified `packages/*/README.md`
files use `ci.yml?branch=main`.
2026-02-18 11:52:07 +01:00
Rózsa Zoltán
ed52d3e6c9
feat: handle backslash in @utility name (#19626)
Resolves #19607

<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

I believe there is no obstacle to simply ignoring backslashes in the
name. This way, various validators - which are not aware of Tailwind
CSS's specific syntax (which allows the `/` character to be used
directly in utility names) - can still be bypassed using backslashes.
For example, instead of `@utility push-1/2`, one could use `@utility
push-1\/2`, while the end result would be identical.

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

I took a previous utility test as a baseline and extended it with
backslashes, and I expect the same result in the output as in the
original test case:

*
https://github.com/rozsazoltan/tailwindcss/blob/main/packages/tailwindcss/src/utilities.test.ts#L28553-L28567
(the original test case I started from)
*
https://github.com/rozsazoltan/tailwindcss/blob/feat/handle-blackslash-in-utility-name/packages/tailwindcss/src/index.test.ts#L4640-L4654
(the current PR's test)

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-02-17 21:12:20 +00:00
Andrey Viktorov
bc6e4b85bf
Fallback to config.createResolver for client and ssr environments in @tailwindcss/vite (#19679)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

Sometimes even if Vite Envrionment API is available, some plugins are
still override `config.createResolver` function to inject own aliases

Since technically `config.createResolver` was only [properly
deprecated](https://github.com/vitejs/vite/pull/20031) in Vite 7.0.0,
it's still a valid(-ish) to do so, even if it wasn't ever officially
supported

Vite already handles this in its internal css resolvers, but not exposes
the code to do so as part of public API, so I've copied and adapted it

Fixes #19677

## Test plan

Tested by copying built package into my repro from the issue, also ran
vite integration tests

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-02-17 19:59:57 +00:00
Robin Malfait
095ff96ba3
Improve performance in bigger projects (#19632)
This PR improves the performance of Oxide when scanning large codebases.

The `Oxide` API, looks something like this:

```ts
let scanner = new Scanner({ sources })
let candidates = scanner.scan() // Found candidates
let files = scanner.files // Scanned files
let globs = scanner.globs // Scanned globs
```

The `files` and `globs` are used to tell PostCSS, Vite, webpack etc
which files to watch for changes.
The `.scan()` operation extracts the candidates from the source files.
You can think of these as potential Tailwind CSS classes.

In all these scenarios we have to walk the file system and find files
that match the `sources`.

### 1. Prevent multiple file system walks

The first big win came from the fact that accessing `.files` after a
`.scan()` also does an entire walk of the file system (for the given
`sources`), which is unnecessary because we just walked the file system.

This is something that's not really an issue in smaller codebases
because we have `mtime` tracking. We don't re-scan a file if its `mtime`
hasn't changed since the last scan. However, in large codebases with
thousands of files, even walking the file system to check `mtime`s can
be expensive.

### 2. Use parallel file system walking

Another big win is to use a parallel file system walker instead of a
synchronous one. The big problem here is that the parallel build has
20ms-50ms of overhead which is noticeable on small codebases. We don't
really know if you have a small or big codebase ahead of time, so maybe
some kind of hint in the future would be useful.

So the solution I settled on right now is to use a synchronous walker
for the initial scan, and then switch to a parallel walker for
subsequent scans (think dev mode). This gives us the best of both
worlds: fast initial scan on small codebases, and fast re-scans on large
codebases.

Caveat: if you use the `@tailwindcss/cli` we know exactly which files
changed so we can just re-scan those files directly without walking the
file system at all. But in `@tailwindcss/postcss` we don't know which
files changed, so we have to walk the file system to check `mtime`s.

While this improvement is nice, it resulted in an annoying issue related
to `mtime` tracking. Since the parallel walker processes files in
parallel, the `mtime` was typed as `Arc<Mutex<FxHashMap<PathBuf,
SystemTime>>>` so to avoid locking, I decided to only walk the files
here and collect their paths. Then later we check the `mtime` to know
whether to re-scan them or not.

Initially I just removed the `mtime` tracking altogether. But it did
have an impact when actually extracting candidates from those files, so
I added it back later.

### 3. Delaying work

I was still a bit annoyed by the fact that we had to track `mtime`
values for every file. This seems like annoying overhead, especially
when doing a single build (no dev mode).

So the trick I applied here is to only start tracking `mtime` values
after the initial scan.

This means that, in dev mode, we would do this:

1. Walk entire file system to track files.
2. On a subsequent scan, walk entire file system (again) and start
tracking `mtime` values. This time, we use the parallel walker instead
of the synchronous one.
3. On further scans, only re-scan files whose `mtime` has changed

The trade-off here is that on the second scan we always re-scan all
files, even if they haven't changed. Since this typically only happens
in dev mode, I think this is an acceptable trade-off especially if the
initial build is therefor faster this way.

### 3. Small wins

There are also a few small wins in here that I would like to mention but
that are less significant:

1. Pre-computed normalized `source` patterns instead of in every walker
filter call.
2. Tried to avoid some allocations in various places. For example the
`pre_process_input` always called `content.to_vec()` which allocates.
Instead we now accept an owned `Vec<u8>` so we don't have to call
`.to_vec()` in the default case (in my testing, this is ~92% of the time
in the codebases I checked).
3. Made the `Cursor` struct smaller, which is used a lot during
candidate extraction.

### Benchmarks

Now for the fun stuff, the benchmarks!

<details>

<summary>The code for the benchmarks</summary>

```ts
import path from 'node:path'
import { bench, boxplot, do_not_optimize, run, summary } from 'mitata'

import { Scanner as ScannerPr } from '/path/to/repo/with/pr/branch/tailwindcss/crates/node'
import { Scanner as ScannerMain } from '/path/to/repo/with/main/branch/tailwindcss/crates/node'

let base = '/path/to/some/codebase'
let sources = [{ base, pattern: '**/*', negated: false }]

// Verify the results are the same before benchmarking
let scannerPr = new ScannerPr({ sources })
let scannerMain = new ScannerMain({ sources })

{
  let aCandidates = scannerPr.scan()
  let bCandidates = scannerMain.scan()

  if (aCandidates.length !== bCandidates.length) {
    throw new Error(`Mismatch in candidate count: ${aCandidates.length} vs ${bCandidates.length}`)
  }

  for (let i = 0; i < aCandidates.length; i++) {
    if (aCandidates[i] !== bCandidates[i]) {
      throw new Error(`Mismatch in candidate at index ${i}: ${aCandidates[i]} vs ${bCandidates[i]}`)
    }
  }

  let aFiles = scannerPr.files
  let bFiles = scannerMain.files

  if (aFiles.length !== bFiles.length) {
    throw new Error(`Mismatch in file count: ${aFiles.length} vs ${bFiles.length}`)
  }

  for (let i = 0; i < aFiles.length; i++) {
    if (aFiles[i] !== bFiles[i]) {
      throw new Error(`Mismatch in file at index ${i}: ${aFiles[i]} vs ${bFiles[i]}`)
    }
  }

  console.log('Scanned', aFiles.length, 'files')
  console.log('Extracted', aCandidates.length, 'candidates')
  console.log('Base =', base)
  console.log()
}

summary(() => {
  boxplot(() => {
    bench('PR (build, .scan()))', function* () {
      yield {
        [0]() {
          return new ScannerPr({ sources })
        },
        bench(scanner: ScannerPr) {
          do_not_optimize(scanner.scan())
        },
      }
    })

    bench('main (build, .scan()))', function* () {
      yield {
        [0]() {
          return new ScannerMain({ sources })
        },
        bench(scanner: ScannerMain) {
          do_not_optimize(scanner.scan())
        },
      }
    })
  })
})

summary(() => {
  boxplot(() => {
    bench('PR (build, .scan() + .files)', function* () {
      yield {
        [0]() {
          return new ScannerPr({ sources })
        },
        bench(scanner: ScannerPr) {
          do_not_optimize(scanner.scan())
          do_not_optimize(scanner.files)
        },
      }
    })

    bench('main (build, .scan() + .files)', function* () {
      yield {
        [0]() {
          return new ScannerMain({ sources })
        },
        bench(scanner: ScannerMain) {
          do_not_optimize(scanner.scan())
          do_not_optimize(scanner.files)
        },
      }
    })
  })
})

summary(() => {
  boxplot(() => {
    bench('PR (watch, .scan()))', function* () {
      yield {
        bench() {
          do_not_optimize(scannerPr.scan())
        },
      }
    })

    bench('main (watch, .scan()))', function* () {
      yield {
        bench() {
          do_not_optimize(scannerMain.scan())
        },
      }
    })
  })
})

summary(() => {
  boxplot(() => {
    bench('PR (watch, .scan() + .files)', function* () {
      yield {
        bench() {
          do_not_optimize(scannerPr.scan())
          do_not_optimize(scannerPr.files)
        },
      }
    })

    bench('main (watch, .scan() + .files)', function* () {
      yield {
        bench() {
          do_not_optimize(scannerMain.scan())
          do_not_optimize(scannerMain.files)
        },
      }
    })
  })
})

await run()
```

</details>

#### tailwindcss.com codebase

```
Scanned 462 files
Extracted 13200 candidates
Base = /Users/robin/github.com/tailwindlabs/tailwindcss.com

clk: ~3.09 GHz
cpu: Apple M1 Max
runtime: bun 1.3.3 (arm64-darwin)
```

In these benchmarks the `PR` one is consistently faster than `main`.
It's not by a lot but that's mainly because the codebase itself isn't
that big. It is a codebase with _a lot_ of candidates though, but not
that many files.

The candidate extraction was already pretty fast, so the wins here
mainly come from avoiding re-walking the file system when accessing
`.files`, and from delaying `mtime` tracking until after the initial
scan.

**Single initial build**:

It's not a lot, but it's a bit faster. This is due to avoiding tracking
the `mtime` values initially and making some small optimizations related
to the struct size and allocations.

```
benchmark                     avg (min … max) p75 / p99    (min … top 1%)
--------------------------------------------- -------------------------------
PR (build, .scan()))            22.87 ms/iter  23.28 ms               █
                        (21.49 ms … 25.68 ms)  23.98 ms     ▂  ▂   ▂  █  ▂▂
                      (832.00 kb …   2.69 mb)   1.41 mb ▆▆▆▆█▆▆█▁▆▆█▁▁█▁▆██▁▆

main (build, .scan()))          25.67 ms/iter  26.12 ms █   █      █
                        (24.54 ms … 27.74 ms)  27.06 ms █   █ █    ███
                      (432.00 kb …   2.78 mb) 996.00 kb ██▁████▁█▁████▁█▁▁█▁█

                               ┌                                            ┐
                               ╷    ┌─────┬──┐     ╷
          PR (build, .scan())) ├────┤     │  ├─────┤
                               ╵    └─────┴──┘     ╵
                                                        ╷  ┌─────┬──┐       ╷
        main (build, .scan()))                          ├──┤     │  ├───────┤
                                                        ╵  └─────┴──┘       ╵
                               └                                            ┘
                               21.49 ms           24.28 ms           27.06 ms

summary
  PR (build, .scan()))
   1.12x faster than main (build, .scan()))
```


**Single initial build + accessing `.files`**:

We don't have to re-walk the entire file system even if we're just
dealing with ~462 scanned files.

```
benchmark                     avg (min … max) p75 / p99    (min … top 1%)
--------------------------------------------- -------------------------------
PR (build, .scan() + .files)    22.54 ms/iter  22.99 ms █      ▂
                        (21.41 ms … 25.86 ms)  24.26 ms █ ▅  ▅▅█▅  ▅▅
                      (368.00 kb …   2.05 mb) 853.00 kb █▇█▇▇████▇▇██▁▁▇▁▁▇▁▇

main (build, .scan() + .files)  32.15 ms/iter  32.17 ms  █  ▂
                        (30.78 ms … 36.22 ms)  35.75 ms ▅█ ▅█  ▅
                      (400.00 kb …   2.45 mb) 952.00 kb ██▁██▇▇█▇▁▁▁▁▁▁▁▁▁▁▁▇

                               ┌                                            ┐
                               ╷┌──┬┐   ╷
  PR (build, .scan() + .files) ├┤  │├───┤
                               ╵└──┴┘   ╵
                                                            ╷┌───┬          ╷
main (build, .scan() + .files)                              ├┤   │──────────┤
                                                            ╵└───┴          ╵
                               └                                            ┘
                               21.41 ms           28.58 ms           35.75 ms

summary
  PR (build, .scan() + .files)
   1.43x faster than main (build, .scan() + .files)
```


**Watch/dev mode, only scanning**:

This now switches to the parallel walker, but since it's not a super big
codebase we don't see a huge win here yet.

```
benchmark                     avg (min … max) p75 / p99    (min … top 1%)
--------------------------------------------- -------------------------------
PR (watch, .scan()))             6.85 ms/iter   7.22 ms   █▄
                          (6.34 ms … 7.94 ms)   7.91 ms  ▄██▃
                      ( 64.00 kb … 688.00 kb) 452.82 kb ▃████▆▂▂▁▂▁▂▁▁▁▅█▆▃▅▃

main (watch, .scan()))           7.92 ms/iter   8.08 ms   █ █  ▃ █▃▃
                          (7.41 ms … 8.71 ms)   8.68 ms   █▆█▆▃█████
                      (  0.00  b …  64.00 kb)  19.20 kb ▆▄██████████▆▁▆▆█▄▄▄▆

                               ┌                                            ┐
                               ╷  ┌──────┬──────┐            ╷
          PR (watch, .scan())) ├──┤      │      ├────────────┤
                               ╵  └──────┴──────┘            ╵
                                                    ╷    ┌───┬──┐           ╷
        main (watch, .scan()))                      ├────┤   │  ├───────────┤
                                                    ╵    └───┴──┘           ╵
                               └                                            ┘
                               6.34 ms            7.51 ms             8.68 ms

summary
  PR (watch, .scan()))
   1.16x faster than main (watch, .scan()))
```

**Watch/dev mode, scanning + accessing `.files`**:

Again we avoid re-walking the entire file system when accessing
`.files`.

```
benchmark                     avg (min … max) p75 / p99    (min … top 1%)
--------------------------------------------- -------------------------------
PR (watch, .scan() + .files)    12.10 ms/iter  12.74 ms █ █     █ █ ▃▃▃
                        (10.69 ms … 13.89 ms)  13.81 ms █ █▂▂▂ ▇█▂█▂███▇
                      (128.00 kb …  10.73 mb)   5.23 mb █▆████▁█████████▆▆▆▆▆

main (watch, .scan() + .files)  14.44 ms/iter  14.74 ms   █
                        (13.93 ms … 15.33 ms)  15.18 ms   ███▅ █ ▅   ▅
                      ( 16.00 kb …  80.00 kb)  39.51 kb █▅████▁███▅▁█████▅▁▅▅

                               ┌                                            ┐
                               ╷      ┌──────┬──────┐         ╷
  PR (watch, .scan() + .files) ├──────┤      │      ├─────────┤
                               ╵      └──────┴──────┘         ╵
                                                               ╷ ┌───┬──┐   ╷
main (watch, .scan() + .files)                                 ├─┤   │  ├───┤
                                                               ╵ └───┴──┘   ╵
                               └                                            ┘
                               10.69 ms           12.93 ms           15.18 ms

summary
  PR (watch, .scan() + .files)
   1.19x faster than main (watch, .scan() + .files)
```

#### Synthetic 5000 files codebase

Based on the instructions from #19616 I created a codebase with 5000
files. Each file contains a `flex` class and a unique class like
`content-['/path/to/file']` to ensure we have a decent amount of unique
candidates.

You can test the script yourself by running this:
```
mkdir -p fixtures/app-5000/src/components/{auth,dashboard,settings,profile,notifications,messages,search,navigation,footer,sidebar}/sub{001..500} && for dir in fixtures/app-5000/src/components/*/sub*; do echo "export const Component = () => <div className=\"flex content-['$dir']\">test</div>" > "$dir/index.tsx"; done && find fixtures/app-5000/src/components -type f | wc -lc
```

```
Scanned 5000 files
Extracted 5005 candidates
Base = /Users/robin/github.com/RobinMalfait/playground/scanner-benchmarks/fixtures/app-5000

clk: ~3.08 GHz
cpu: Apple M1 Max
runtime: bun 1.3.3 (arm64-darwin)
```

**Single initial build**:

As expected not a super big win here because it's a single build. But
there is a noticeable improvement.

```
benchmark                     avg (min … max) p75 / p99    (min … top 1%)
--------------------------------------------- -------------------------------
PR (build, .scan()))           217.27 ms/iter 211.97 ms              █
                      (205.99 ms … 289.53 ms) 214.33 ms ▅      ▅ ▅▅▅▅█▅▅    ▅
                      (  3.34 mb …   4.25 mb)   3.72 mb █▁▁▁▁▁▁█▁███████▁▁▁▁█

main (build, .scan()))         249.26 ms/iter 239.88 ms               █
                      (231.51 ms … 381.66 ms) 241.01 ms ▅    ▅ ▅▅  ▅  █▅  ▅▅▅
                      (  4.22 mb …   4.78 mb)   4.49 mb █▁▁▁▁█▁██▁▁█▁▁██▁▁███

                               ┌                                            ┐
                               ╷    ┌─────╷──┬
          PR (build, .scan())) ├────┤     ┤  │
                               ╵    └─────╵──┴
                                                                ╷   ┌───────╷
        main (build, .scan()))                                  ├───┤       ┤
                                                                ╵   └───────╵
                               └                                            ┘
                               205.99 ms         223.50 ms          241.01 ms

summary
  PR (build, .scan()))
   1.15x faster than main (build, .scan()))
```

**Single initial build + accessing `.files`**:

Now things are getting interesting. Almost a 2x speedup by avoiding
re-walking the file system when accessing `.files`.

```
benchmark                     avg (min … max) p75 / p99    (min … top 1%)
--------------------------------------------- -------------------------------
PR (build, .scan() + .files)   216.35 ms/iter 214.53 ms █ █ █
                      (211.00 ms … 242.64 ms) 221.45 ms █ █▅█ ▅▅      ▅     ▅
                      (  2.97 mb …   4.47 mb)   3.97 mb █▁███▁██▁▁▁▁▁▁█▁▁▁▁▁█

main (build, .scan() + .files) 414.79 ms/iter 406.05 ms     ██
                      (396.72 ms … 542.30 ms) 413.69 ms ▅   ██▅ ▅▅ ▅ ▅      ▅
                      (  5.19 mb …   6.03 mb)   5.63 mb █▁▁▁███▁██▁█▁█▁▁▁▁▁▁█

                               ┌                                            ┐
                               ┌┬╷
  PR (build, .scan() + .files) ││┤
                               └┴╵
                                                                        ╷┌──╷
main (build, .scan() + .files)                                          ├┤  ┤
                                                                        ╵└──╵
                               └                                            ┘
                               211.00 ms         312.34 ms          413.69 ms

summary
  PR (build, .scan() + .files)
   1.92x faster than main (build, .scan() + .files)
```

**Watch/dev mode, only scanning**:

This is where we see bigger wins because now we're using the parallel
walker.

```
benchmark                     avg (min … max) p75 / p99    (min … top 1%)
--------------------------------------------- -------------------------------
PR (watch, .scan()))            76.26 ms/iter  77.41 ms                     █
                        (73.56 ms … 79.02 ms)  77.81 ms ▅   ▅  ▅ ▅▅ ▅▅▅   ▅ █
                      (  2.53 mb …   5.52 mb)   3.06 mb █▁▁▁█▁▁█▁██▁███▁▁▁█▁█

main (watch, .scan()))         166.71 ms/iter 165.14 ms █  █
                      (161.49 ms … 198.26 ms) 168.99 ms █ ▅█ ▅▅▅  ▅  ▅      ▅
                      (  1.08 mb …   2.72 mb)   1.24 mb █▁██▁███▁▁█▁▁█▁▁▁▁▁▁█

                               ┌                                            ┐
                               ╷┬┐
          PR (watch, .scan())) ├││
                               ╵┴┘
                                                                        ╷┌─┬╷
        main (watch, .scan()))                                          ├┤ │┤
                                                                        ╵└─┴╵
                               └                                            ┘
                               73.56 ms          121.28 ms          168.99 ms

summary
  PR (watch, .scan()))
   2.19x faster than main (watch, .scan()))
```

**Watch/dev mode, scanning + accessing `.files`**:

This is the biggest win of them all because we have all the benefits
combined:

1. Avoiding re-walking the file system when accessing `.files`
2. Using the parallel walker for faster file system walking

```
benchmark                     avg (min … max) p75 / p99    (min … top 1%)
--------------------------------------------- -------------------------------
PR (watch, .scan() + .files)    84.04 ms/iter  84.84 ms            █
                        (80.96 ms … 87.53 ms)  87.27 ms ▅▅   ▅▅ ▅▅ █▅ ▅     ▅
                      ( 15.42 mb …  31.34 mb)  22.16 mb ██▁▁▁██▁██▁██▁█▁▁▁▁▁█

main (watch, .scan() + .files) 338.59 ms/iter 353.89 ms   █
                      (321.87 ms … 378.43 ms) 358.70 ms   █   █
                      (  2.39 mb …   2.45 mb)   2.42 mb ███▁▁██▁▁▁▁▁▁▁▁▁▁██▁█

                               ┌                                            ┐
                               ┬┐
  PR (watch, .scan() + .files) ││
                               ┴┘
                                                                      ┌──┬─┐╷
main (watch, .scan() + .files)                                        │  │ ├┤
                                                                      └──┴─┘╵
                               └                                            ┘
                               80.96 ms          219.83 ms          358.70 ms

summary
  PR (watch, .scan() + .files)
   4.03x faster than main (watch, .scan() + .files)
```


## Test plan

1. All existing tests still pass
2. All public APIs remain the same
3. In the benchmarks I'm sharing, I first verify that the candidates
returned and the files returned are the same before and after the
change.
4. Benchmarked against real codebases, and against a synthetic large
codebase (5000 files).

Fixes: #19616
2026-02-17 20:15:20 +01:00
Arsher. {Zach}
f212b0fc22
fix: restore full page reload for watched external files on Vite 7.1+ (#19670)
# PR: Fix @source file changes not triggering full page reload on Vite
7.1+

## Description
This PR addresses issue #19637 where template files (PHP, HTML, Blade,
etc.) watched via the `@source` directive fail to trigger a full page
reload when using Vite 7.1 or newer.

## Root Cause
Vite 7.1 introduced the Environment API, which supersedes the legacy
WebSocket API for HMR. Specifically:
- `server.ws.send` is deprecated/ignored for certain external file
updates in favor of `server.hot.send`.
- The `@tailwindcss/vite` plugin currently collects `ViteDevServer`
instances but lacks a `handleHotUpdate` hook to explicitly trigger
reloads for non-module files added via `addWatchFile`.

## Changes
- Implemented a `handleHotUpdate` hook in the `@tailwindcss/vite`
plugin.
- The hook identifies changes to files that are not part of the standard
Vite module graph (e.g., `.php`, `.html`) but are watched by Tailwind.
- Triggers a `full-reload` using the new `server.hot.send` API if
available (Vite 7.1+), with a fallback to `server.ws.send` for backward
compatibility.

## Verification
- Reproduced the issue in a standalone Vite 7.1.0 project using a mock
plugin with the legacy API.
- Confirmed that the browser fails to reload upon editing a watched
`.php` file.
- Verified that migrating to `server.hot.send` restores the expected
reload behavior.

[ci-all]

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-02-17 20:04:17 +01:00
Robin Malfait
6eb3b32434
Allow multiples of .25 in aspect-* fractions (#19688)
This PR reduces the restrictions of the `aspect-*` utilities when
dealing with fractional values.

Up until now, the numbers had to be positive integers, so `aspect-1/2`
was valid, but `aspect-8.5/11` was not.

This PR allows for any multiple of `.25` as a valid value, so
`aspect-8.5/11` is now valid, but `aspect-8.3/11` is not, this will
still require `aspect-[8.3/11]` arbitrary value syntax to be valid.

This behavior of allowing multiples of `.25` is consistent with other
utilities that handle bare values such as `w-2.5`.

## Test plan

1. Existing tests pass
2. Added a test for `aspect-8.5/11`

Fixes: #19663
Closes: #19680, #19669
2026-02-17 19:32:31 +01:00
Benjamin Bock
d74f8b8392
Add '.jj' to ignored content directories (#19687)
Ignore .jj directory, just like .git, .hg, .svn.

https://www.jj-vcs.dev/latest/

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-02-17 17:09:59 +00:00
Pavan Shinde
99a7c602f2
docs: fix GitHub links to tailwindlabs org (#19686)
## Summary 
Fix outdated GitHub links that still point to
`github.com/tailwindcss/tailwindcss` (old org). This updates references
across the README, contributing guide, and PR template to the canonical
`tailwindlabs/tailwindcss` URLs to avoid redirects/404s and keep repo
metadata consistent

## Test Plan
This change is docs-only.
2026-02-17 13:06:49 +01:00
depfu[bot]
257dc581ba
Update @vitejs/plugin-react 5.1.2 → 5.1.3 (patch) (#19651)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (5.1.2 → 5.1.3) ·
[Repo](https://github.com/vitejs/vite-plugin-react) ·
[Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
















---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-02-09 12:20:45 -05:00
depfu[bot]
4614f5eaa7
Update autoprefixer 10.4.23 → 10.4.24 (patch) (#19642)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ autoprefixer (10.4.23 → 10.4.24) ·
[Repo](https://github.com/postcss/autoprefixer) ·
[Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/postcss/autoprefixer/releases/tag/10.4.24">10.4.24</a></h4>

<blockquote><ul dir="auto">
<li>Made Autoprefixer a little faster (by <a
href="https://bounce.depfu.com/github.com/Cherry">@Cherry</a>).</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let
us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="212ba3c189...36692c25e7">See
the full diff on Github</a>. The new version differs by 3 commits:</p>
<ul>
<li><a
href="36692c25e7"><code>Release
10.4.24 version</code></a></li>
<li><a
href="67df014ae1"><code>Update
dependencies</code></a></li>
<li><a
href="032440ee4a"><code>perf:
reduce array allocations (#1542)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-02-09 12:20:25 -05:00
Pavan Shinde
8ed67bf551
Fix Tailwind CSS package README GitHub links (#19644)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary
These package READMEs referenced the wrong GitHub org
(tailwindcss/tailwindcss) and outdated branches (master/next) for common
project links.
Update them to point at tailwindlabs/tailwindcss on main for releases,
license, discussions, and contributing docs.
<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

## Test plan
Docs-only change: No test required.
<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->
2026-02-07 19:26:38 -05:00
Pavan Shinde
e73c2472fd
Fix README links to use main branch (#19641)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary
The README still referenced the `next` branch for the CI badge and
contributing docs link, but the `next` branch no longer exists upstream.
This updates those URLs to point to `main` so the badge and contributing
docs link work correctly.
<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

## Test plan
N/A, docs only change 
<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->
2026-02-06 16:51:07 +01:00
Pavan Shinde
ddb47e84da
Fix README license badge link (#19638)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary
The README license badge linked to a non-existent `master` branch (and
the wrong org).
This updates the link to the current location:
tailwindlabs/tailwindcss/blob/main/LICENSE.
<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

## Test plan
- Opened `README.md` and clicked the license badge/link to confirm it
resolves to the LICENSE file on the `main` branch.
<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->
2026-02-05 10:59:10 -05:00
Robin Malfait
23d01bc474
only notify Discord once
If we're in a matrix, and one of the jobs fail, then we only want to
notify Discord once instead of for each failure.
2026-02-04 13:08:20 +01:00
Robin Malfait
1638f35c3a
Bump dependencies (#19608)
This PR bumps a bunch of dependencies. This also moves a few
dependencies that we use in multiple packages to the pnpm catalog.

Closes: #19603, #19604, #19576, #19575, #19573, #19565, #19547, #19546,
#19545, #19609, #19581, #19620, #19619

- #19603
- #19604
- #19576
- #19575
- #19573
- #19565
- #19547
- #19546
- #19545
- #19609
- #19581
- https://github.com/tailwindlabs/tailwindcss/pull/19620 
- #19620
- #19619

## Test Plan

All tests in CI should still pass. [ci-all]
2026-02-04 12:38:50 +01:00
Robin Malfait
df96ea5eba
Fix infinite loop when using @variant inside @custom-variant that points to another @custom-variant (#19633)
This PR fixes an infinite loop when you use a `@variant` inside of a
`@custom-variant`, where the `@variant` used is another
`@custom-variant`.

The issue stems from the fact that a `@custom-variant` can use a `@slot`
that we have to replace with the proper AST nodes. However in this
setup, the AST nodes will include a `@slot` node as well, which causes
us to replace the `@slot` again, and so on, causing an infinite loop.

```css
@custom-variant a {
  @slot;
}

@custom-variant b {
  @variant a {
    @slot;
  }
}
```

The solution here is to replace the `@slot` nodes and then skip walking
the nodes that were just inserted. This does mean that we end up with a
`@slot` node in the final AST but that's not a real issue because that
will get replaced later when handling the next `@custom-variant`.

## Test plan

1. Existing tests still pass
2. Added a regression test to ensure that the infinite loop does not
happen anymore
3. Added additional tests to ensure that the behavior is correct

Thanks @wongjn for your initial debugging help and providing a test case
as well!

Fixes: #19618
2026-02-02 14:42:31 +01:00
Adam Wathan
d52c94ff5f
Simplify logical sizing utility theme namespaces (#19625)
Update inline-size and block-size utilities to only read from --spacing
and --container theme keys, removing backwards-compat references to
--width, --height, --min-width, --min-height, --max-width, and
--max-height.

Since these are new utilities with no backwards compatibility concerns,
the simpler approach is preferred:
- inline/min-inline/max-inline: --spacing, --container
- block/min-block/max-block: --spacing only

https://claude.ai/code/session_01WhrjmutxsLP753VUtFy24S

<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-30 10:40:51 -05:00
Robin Malfait
d8b00d1114
add concurrency control to workflows
Essentially cancel previous workflows when new commits are pushed.
2026-01-30 15:31:23 +01:00
Adam Wathan
53205f5dc5
Add font-features-* utility for font-feature-settings (#19623)
Add a new arbitrary-value-only utility `font-features-*` that sets the
`font-feature-settings` CSS property. This utility only accepts
arbitrary
values (e.g., `font-features-["smcp"]`,
`font-features-[var(--features)]`).

The utility is sorted directly after `font-family` in the property
order.

https://claude.ai/code/session_01EAccbTHJ9dTUJ53ttq2jc4

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-01-30 14:21:16 +00:00
Adam Wathan
5395c7e764
Add logical inset utilities (inset-s, inset-e, inset-bs, inset-be) (#19613)
## Summary

This PR adds support for logical inset utilities that map to CSS logical
properties:

- `inset-s` → `inset-inline-start`
- `inset-e` → `inset-inline-end`
- `inset-bs` → `inset-block-start`
- `inset-be` → `inset-block-end`

These utilities complement the existing `inset-x` (inline) and `inset-y`
(block) utilities, providing more granular control over positioning in a
direction-aware manner. This aligns with CSS logical properties and
improves support for internationalization (RTL/LTR languages).

### Changes

1. **property-order.ts**: Added `inset-block-start` and
`inset-block-end` to the property ordering list to ensure consistent
cascade ordering
2. **utilities.ts**: Added four new utility mappings for the logical
inset properties
3. **utilities.test.ts**: Added comprehensive test coverage for all four
new utilities, including:
- Valid class generation with various value types (custom spacing,
percentages, arbitrary values)
   - Negative value support
   - Invalid class rejection (malformed syntax, invalid modifiers)

## Test plan

All changes are covered by unit tests in `utilities.test.ts`:
- `inset-s` test: 8 valid classes + 13 invalid class assertions
- `inset-e` test: 8 valid classes + 13 invalid class assertions  
- `inset-bs` test: 8 valid classes + 13 invalid class assertions
- `inset-be` test: 8 valid classes + 13 invalid class assertions

Each test verifies correct CSS output generation and proper rejection of
malformed utilities.

https://claude.ai/code/session_01JcYXVAMawRuntKatjku1WZ

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-01-30 14:13:56 +00:00
Adam Wathan
3d1e654c02
Add logical sizing utilities (inline-size, block-size) (#19612)
## Summary

Adds new utilities for CSS logical properties that provide
writing-direction-aware alternatives to width and height:

- `inline-*` for `inline-size` (logical equivalent of width)
- `min-inline-*` for `min-inline-size` (logical equivalent of min-width)
- `max-inline-*` for `max-inline-size` (logical equivalent of max-width)
- `block-*` for `block-size` (logical equivalent of height)
- `min-block-*` for `min-block-size` (logical equivalent of min-height)
- `max-block-*` for `max-block-size` (logical equivalent of max-height)

## New Utilities

### inline-size utilities

| Class | CSS Property |
|-------|-------------|
| `inline-auto` | `inline-size: auto` |
| `inline-full` | `inline-size: 100%` |
| `inline-min` | `inline-size: min-content` |
| `inline-max` | `inline-size: max-content` |
| `inline-fit` | `inline-size: fit-content` |
| `inline-screen` | `inline-size: 100vw` |
| `inline-svw` | `inline-size: 100svw` |
| `inline-lvw` | `inline-size: 100lvw` |
| `inline-dvw` | `inline-size: 100dvw` |
| `inline-{spacing}` | `inline-size: {value}` (e.g., `inline-4`,
`inline-px`) |
| `inline-{fraction}` | `inline-size: {percent}` (e.g., `inline-1/2`,
`inline-3/4`) |
| `inline-[{value}]` | `inline-size: {value}` (arbitrary values) |

### min-inline-size utilities

| Class | CSS Property |
|-------|-------------|
| `min-inline-auto` | `min-inline-size: auto` |
| `min-inline-full` | `min-inline-size: 100%` |
| `min-inline-min` | `min-inline-size: min-content` |
| `min-inline-max` | `min-inline-size: max-content` |
| `min-inline-fit` | `min-inline-size: fit-content` |
| `min-inline-screen` | `min-inline-size: 100vw` |
| `min-inline-svw` | `min-inline-size: 100svw` |
| `min-inline-lvw` | `min-inline-size: 100lvw` |
| `min-inline-dvw` | `min-inline-size: 100dvw` |
| `min-inline-{spacing}` | `min-inline-size: {value}` |
| `min-inline-[{value}]` | `min-inline-size: {value}` (arbitrary values)
|

### max-inline-size utilities

| Class | CSS Property |
|-------|-------------|
| `max-inline-none` | `max-inline-size: none` |
| `max-inline-full` | `max-inline-size: 100%` |
| `max-inline-min` | `max-inline-size: min-content` |
| `max-inline-max` | `max-inline-size: max-content` |
| `max-inline-fit` | `max-inline-size: fit-content` |
| `max-inline-screen` | `max-inline-size: 100vw` |
| `max-inline-svw` | `max-inline-size: 100svw` |
| `max-inline-lvw` | `max-inline-size: 100lvw` |
| `max-inline-dvw` | `max-inline-size: 100dvw` |
| `max-inline-{spacing}` | `max-inline-size: {value}` |
| `max-inline-[{value}]` | `max-inline-size: {value}` (arbitrary values)
|

### block-size utilities

| Class | CSS Property |
|-------|-------------|
| `block-auto` | `block-size: auto` |
| `block-full` | `block-size: 100%` |
| `block-min` | `block-size: min-content` |
| `block-max` | `block-size: max-content` |
| `block-fit` | `block-size: fit-content` |
| `block-screen` | `block-size: 100vh` |
| `block-lh` | `block-size: 1lh` |
| `block-svh` | `block-size: 100svh` |
| `block-lvh` | `block-size: 100lvh` |
| `block-dvh` | `block-size: 100dvh` |
| `block-{spacing}` | `block-size: {value}` (e.g., `block-4`,
`block-px`) |
| `block-{fraction}` | `block-size: {percent}` (e.g., `block-1/2`,
`block-3/4`) |
| `block-[{value}]` | `block-size: {value}` (arbitrary values) |

### min-block-size utilities

| Class | CSS Property |
|-------|-------------|
| `min-block-auto` | `min-block-size: auto` |
| `min-block-full` | `min-block-size: 100%` |
| `min-block-min` | `min-block-size: min-content` |
| `min-block-max` | `min-block-size: max-content` |
| `min-block-fit` | `min-block-size: fit-content` |
| `min-block-screen` | `min-block-size: 100vh` |
| `min-block-lh` | `min-block-size: 1lh` |
| `min-block-svh` | `min-block-size: 100svh` |
| `min-block-lvh` | `min-block-size: 100lvh` |
| `min-block-dvh` | `min-block-size: 100dvh` |
| `min-block-{spacing}` | `min-block-size: {value}` |
| `min-block-[{value}]` | `min-block-size: {value}` (arbitrary values) |

### max-block-size utilities

| Class | CSS Property |
|-------|-------------|
| `max-block-none` | `max-block-size: none` |
| `max-block-full` | `max-block-size: 100%` |
| `max-block-min` | `max-block-size: min-content` |
| `max-block-max` | `max-block-size: max-content` |
| `max-block-fit` | `max-block-size: fit-content` |
| `max-block-screen` | `max-block-size: 100vh` |
| `max-block-lh` | `max-block-size: 1lh` |
| `max-block-svh` | `max-block-size: 100svh` |
| `max-block-lvh` | `max-block-size: 100lvh` |
| `max-block-dvh` | `max-block-size: 100dvh` |
| `max-block-{spacing}` | `max-block-size: {value}` |
| `max-block-[{value}]` | `max-block-size: {value}` (arbitrary values) |

## Test plan

- [x] Added comprehensive unit tests for all new utilities
- [ ] Verify utilities work correctly in RTL layouts
- [ ] Test with vertical writing modes

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-01-30 13:46:37 +00:00
Adam Wathan
991072fcac
Add logical block-start/block-end border, margin, and padding utilities (#19601)
## Summary

This PR adds support for logical block-start and block-end CSS
properties in Tailwind CSS utilities. These properties are part of the
CSS Logical Properties specification and provide writing-mode-aware
alternatives to physical top/bottom properties.

The following new utilities are added:
- **Border utilities**: `border-bs-*` and `border-be-*` (for
`border-block-start` and `border-block-end`)
- **Margin utilities**: `mbs-*` and `mbe-*` (for `margin-block-start`
and `margin-block-end`)
- **Scroll margin utilities**: `scroll-mbs-*` and `scroll-mbe-*` (for
`scroll-margin-block-start` and `scroll-margin-block-end`)
- **Scroll padding utilities**: `scroll-pbs-*` and `scroll-pbe-*` (for
`scroll-padding-block-start` and `scroll-padding-block-end`)
- **Padding utilities**: `pbs-*` and `pbe-*` (for `padding-block-start`
and `padding-block-end`)

These utilities follow the same patterns as their existing
inline-start/inline-end counterparts and support all standard modifiers
(arbitrary values, negative values, opacity modifiers, etc.).

## Changes

1. **utilities.ts**: Added new utility definitions for all
block-start/block-end properties
2. **property-order.ts**: Updated CSS property ordering to include the
new logical properties in the correct cascade order
3. **utilities.test.ts**: Added comprehensive test cases for all new
utilities
4. **utilities.test.ts.snap**: Updated snapshots showing generated CSS
for the new utilities

## Test plan

All changes are covered by existing test infrastructure:
- Unit tests verify correct CSS generation for each utility variant
- Snapshot tests validate the complete output for border utilities
- Invalid modifier combinations are tested to ensure proper error
handling
- Tests cover standard values, arbitrary values, negative values, and
opacity modifiers

https://claude.ai/code/session_01V89HxEtppGVvMtuPbYrayM

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-01-30 14:37:51 +01:00
Robin Malfait
566eea4ff9
Improve walk performance (#19614)
This PR improves the walk performance by a little bit, but also reduces
memory usage. It's a tiny change and all the APIs remain the same. Not
the most important PR, but we had this idea on the backburner so we
decided to just implement it.

Thanks @thecrypticace!

## Test plan

- All the existing tests should pass.

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2026-01-29 15:49:31 +01:00
Tim Neutkens
e3e85b364f
[tailwindcss-vite] Add Register dependency messages logline (#19611)
The PostCSS implementation has a debug logline for registering
dependencies but this is missing for @tailwindcss/vite.

<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

Adds the "Register dependency messages" instrumentation span to the Vite
plugin's dependency registration code, matching the existing
implementation in @tailwindcss/postcss.

This ensures consistent debug instrumentation across both bundler
integrations when DEBUG is enabled.

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->
2026-01-29 14:17:50 +00:00
Tim Neutkens
bccf4bbfbd
Add @tailwindcss/webpack loader for Tailwind CSS v4 (#19610)
## Summary

This PR adds a new `@tailwindcss/webpack` package that provides a
dedicated webpack loader for Tailwind CSS v4. This loader works with
both standard webpack and Turbopack's webpack loader compatibility
layer.

### Why a dedicated loader?

The current webpack integration uses `postcss-loader` +
`@tailwindcss/postcss`. While this works, a dedicated loader:

- **Eliminates PostCSS as a middleman** - works directly with CSS
strings (no AST conversions)
- **Simpler and more efficient** - follows the same pattern as
`@tailwindcss/vite`
- **Better for Turbopack** - gives direct control over dependency
reporting via webpack's loader API

### How it works

The loader mirrors the Vite plugin's approach:

1. Uses `compile()` from `@tailwindcss/node` to parse CSS and resolve
`@apply` directives
2. Uses `Scanner` from `@tailwindcss/oxide` to scan content files for
utility candidates
3. Reports dependencies via `this.addDependency()` and
`this.addContextDependency()`
4. Optionally optimizes output with Lightning CSS

### Usage

```javascript
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /.css$/i,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          '@tailwindcss/webpack',  // No PostCSS needed!
        ],
      },
    ],
  },
}
```

### Options

- `base` - The base directory to scan for class candidates (defaults to
`process.cwd()`)
- `optimize` - Whether to optimize/minify the output CSS (defaults to
`true` in production)

### Files added

- `packages/@tailwindcss-webpack/` - New package
  - `src/index.ts` - Main loader implementation
  - `src/index.cts` - CommonJS entry point for webpack compatibility
  - `package.json`, `tsconfig.json`, `tsup.config.ts`, `README.md`
- `integrations/webpack/loader.test.ts` - Integration tests
- `integrations/utils.ts` - Added webpack override for transitive
dependencies

### Test plan

- [x] Build test - verifies basic compilation
- [x] Watch test - verifies HMR when adding new Tailwind classes
- [x] `@apply` test - verifies `@apply` directives work correctly
- [x] Optimization test - verifies minification works

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-01-29 14:16:31 +00:00
Robin Malfait
79711678fa
workaround: increase some CI timeouts 2026-01-06 20:58:00 +01:00
Robin Malfait
b8454b0b3f
Revert "temporarily disable discord notifications"
This reverts commit 7d78fda879.
2026-01-06 16:40:50 +01:00
Robin Malfait
22c0618583
Revert "re-run integration tests in CI if they fail"
This reverts commit efd75c1abb.
2026-01-06 16:23:56 +01:00
Robin Malfait
4b507b0a23
Revert "retry integration tests 3 times before actually failing"
This reverts commit ffcb96a523.
2026-01-06 16:23:46 +01:00
Robin Malfait
efd75c1abb
re-run integration tests in CI if they fail
This prevents the CI job from having to restart the entire job.
2026-01-06 16:17:55 +01:00
Robin Malfait
309d7f6e8a
bump nuxt dependency in integration tests 2026-01-06 16:17:55 +01:00
Robin Malfait
ffcb96a523
retry integration tests 3 times before actually failing
This is not ideal, but it prevents some manual work when CI is a bit too
flaky on the integration tests. This also prevents the CI job from
re-running from scratch which should speed up the process as well.
2026-01-06 16:17:54 +01:00
Robin Malfait
197d21c438
remove .npmrc with auto-install-peers=true
This was necessary in previous versions of Node, but now that we use
Node 24 in CI you get the following warning:

> npm warn Unknown env config "auto-install-peers". This will stop working in the next major version of npm.

So... let's get rid of it.
2026-01-06 15:28:25 +01:00
Robin Malfait
80e35e8f38
pin @napi-rs/cli
Newer versions of `@napi-rs/cli` (I noticed it in 3.5.1) rely on `@inquirer/core` for the CLI.
This version requires a modern Node version because it relies on `import
{ styleText } from 'node:util';` which is not supported on older Node
versions (<20).

The issue now is that we rely on a Docker image on ghcr
(ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine) which ships with Node
v18 which is too old and would crash when trying to build on CI.

Once ghcr is updated with a newer embedded Node version, we can bump
`@napi-rs/cli` again.

But for now, it's pinned.
2026-01-06 15:19:59 +01:00
Robin Malfait
60c6255fb8
use older version of @napi-rs/cli 2026-01-06 15:12:32 +01:00
Robin Malfait
ae130776ab
Revert "pin musl-related docker images"
This reverts commit 26515af2d5.
2026-01-06 15:11:38 +01:00
Robin Malfait
26515af2d5
pin musl-related docker images 2026-01-06 15:07:42 +01:00
Robin Malfait
7d78fda879
temporarily disable discord notifications 2026-01-06 14:37:46 +01:00
Robin Malfait
c6f0c6b318
bump required engine to Node 20 2026-01-06 13:57:52 +01:00
Robin Malfait
6313cf6770
bump to Node 24 in CI
All bumped `actions/*` for GitHub Workflows require Node 24 right now.
We were relying on Node 20, but that's in maintenance mode until April.

Node 24 is the current Active LTS version.
2026-01-06 13:48:17 +01:00
Robin Malfait
bde403e013
bump actions/github-script to v8 2026-01-06 13:46:44 +01:00
Robin Malfait
562acdac54
bump actions/cache to v5 2026-01-06 13:45:57 +01:00
Robin Malfait
d214c30161
bump actions/download-artifact to v6 2026-01-06 13:44:23 +01:00
Robin Malfait
bd7fe7abc8
bump actions/upload-artifact to v6 2026-01-06 13:43:59 +01:00
Robin Malfait
4f76b47f40
bump actions/checkout to v6 2026-01-06 13:41:55 +01:00
Robin Malfait
7107c04713
bump actions/setup-node to v6 2026-01-06 13:39:44 +01:00
Robin Malfait
4719dd9848
Revert "run release workflows against newer Node version"
This reverts commit 1f9ac73daf.
2026-01-06 13:37:35 +01:00
Robin Malfait
1f9ac73daf
run release workflows against newer Node version
When using NAPI to build for release purposes, it relies on `inquirer`,
which relies on `@inquirer/core` which relies on a modern Node version
to support `import { styleText } from 'node:util'`

We want to still make sure that CI / Integration Tests pass on Node v20
(which we currently do), so just bumping the prepare-release.yml,
release-insiders.yml and release.yml workflows.

If this isn't enough, I'll properly drop the `@napi-rs/cli` version or
force an `inquirer`/`@inquirer/core` version. Let's try this first.
2026-01-06 13:25:14 +01:00
Robin Malfait
f82ac39ff2
Improve @utility name validation (#19524)
This PR improves the validation of allowed `@utility …` names.

Each `@utility` name should be a valid Tailwind CSS class, so new
syntaxes should not be allowed, e.g. `foo/bar/baz` would be invalid.

We already enforce this behavior but not consistently. The Oxide scanner
that scans all your source files for potential Tailwind CSS classes does
enforce all of these rules already. So if you used `@utility foo/bar/baz
{}`, the Oxide scanner would not pick up `foo/bar/baz` as a valid class
name, so for that reason it's not a breaking change.

Where we didn't enforce it is in places where you use the
development-only CDN or Tailwind Play. That's because those environments
don't use the Oxide at all, and get the classes from the DOM directly
and pass it to Tailwind's compiler.

This PR moves some of these validation rules into Tailwind's core when
defining custom `@utility` utilities.

Fixes: #19505

### Test plan

1. Existing tests still pass
2. Added a regression test for the linked issue
3. Added new tests with valid / invalid `@utility` names

I also confirmed with Oxide to know which classes were actually valid
and which ones are invalid.

Given this input CSS:
```css
@utility foo { color: red }
@utility foo_ { color: red }     /* This one looks invalid to me, but it works today */
                                 /* and I don't want to introduce unnecessary breaking changes. */
@utility foo-1.5 { color: red }
@utility foo-123 { color: red }
@utility -foo { color: red }
@utility foo-bar { color: red }
@utility foo_bar { color: red }
@utility foo-50% { color: red }
@utility foo-1/2 { color: red }
```

And this HTML:
```html
<!-- Extracted: -->
<div class="foo foo_ foo-123 -foo foo-bar foo_bar foo-50% foo-1/2 foo-1.5"></div>

<!-- Not Extracted: -->
<div class="Foo -Foo foo-1/ foo- foo-p% foo-1..5 foo.bar foo..bar "></div>
```

Then all classes in the `Extracted` section are found. One funny thing
in the not extracted section is that the `bar` in `foo.bar` and
`foo..bar` is also extracted. Feels like a potential bug, but out of
scope for this PR.

<img width="766" height="164" alt="image"
src="https://github.com/user-attachments/assets/fafbaa35-2730-4f61-9b15-6690b02ec686"
/>
2026-01-06 12:54:43 +01:00
depfu[bot]
dc432aaa18
Update @napi-rs/wasm-runtime 1.1.0 → 1.1.1 (patch) (#19527)
<!--depfu-start-->
> 👉 **This PR is queued up to get rebased by Depfu**
<!--depfu-end-->





Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​napi-rs/wasm-runtime (1.1.0 → 1.1.1) ·
[Repo](https://github.com/napi-rs/napi-rs)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-01-06 11:49:41 +00:00
Robin Malfait
25f7ccff80
Fix class extraction for Rails' strict locals (#19525)
Fixes: #19481

This PR improves the Ruby extractor to better handle strict locals. We
recently introduced skipping comments in the Ruby extractor (PR #19243
for #19239) by ignoring comments that start with `#` until the end of
the line.

Strict locals are implemented like this:
```ruby
<%# locals: (css: "text-amber-600") %>
```

Notice the `#` after the `<%`, we considered this a comment and ignored
it.

This PR changes that behavior slightly where we skip comments that are
preceded by `%`. This means that `<%# anything here _will_ be scanned
%>`. This should solve the strict locals case, and normal comments will
still be skipped.

We can be more strict in the future if needed, but I think that this
should be a good solution for both scenarios.

### Test plan

1. Added a test to ensure we extract candidates in strict locals
2. Added a regression test for issue #19239 where we introduced skipping
comments in the Ruby extractor
3. Other existing tests are still passing

We can also verify the extracted candidates:
(it's subtle, but you can see that the class is being extracted now)

<img width="1187" height="1376" alt="image"
src="https://github.com/user-attachments/assets/74bbfd79-9db4-4a5b-bd8d-25f1565c6bfd"
/>
2026-01-06 12:46:14 +01:00
depfu[bot]
d5beb9534e
Update @napi-rs/cli 3.4.1 → 3.5.1 (minor) (#19528)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​napi-rs/cli (3.4.1 → 3.5.1) ·
[Repo](https://github.com/napi-rs/napi-rs)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-01-06 12:44:27 +01:00
depfu[bot]
c07d4a749d
Update turbo 2.6.3 → 2.7.2 (minor) (#19509)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ turbo (2.6.3 → 2.7.2) ·
[Repo](https://github.com/turborepo/turbo)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2026-01-05 07:46:49 -05:00
Akash Agarwal
9720692eda
Fix typo in comment (#19493) 2025-12-27 11:56:29 -05:00
depfu[bot]
fbc358cec1
Update prettier-plugin-embed 0.5.0 → 0.5.1 (minor) (#19492)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ prettier-plugin-embed (0.5.0 → 0.5.1) ·
[Repo](https://github.com/Sec-ant/prettier-plugin-embed) ·
[Changelog](https://github.com/Sec-ant/prettier-plugin-embed/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/Sec-ant/prettier-plugin-embed/releases/tag/v0.5.1">0.5.1</a></h4>

<blockquote><h3 dir="auto">Patch Changes</h3>
<ul dir="auto">
<li>
<a
href="0c1b3c457c"><tt>0c1b3c4</tt></a>:
Fix indentation issues in sql, css and markdown embedded languages.</li>
<li>
<a
href="0c1b3c457c"><tt>0c1b3c4</tt></a>:
Fix embedded xml and prisma formatting behavior.</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="488a11218e...41d171dfd7">See
the full diff on Github</a>. The new version differs by 3 commits:</p>
<ul>
<li><a
href="41d171dfd7"><code>chore(release):
v0.5.1 (#158)</code></a></li>
<li><a
href="80173f4eb7"><code>fix(ci):
test workflow</code></a></li>
<li><a
href="0c1b3c457c"><code>fix:
refactor embedded options and embedder logic (#157)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-26 12:36:51 -05:00
depfu[bot]
fba35d01bb
Update @types/bun 1.3.3 → 1.3.5 (patch) (#19491)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/bun (1.3.3 → 1.3.5) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-25 18:22:13 -05:00
depfu[bot]
e8a5a7f092
Update @tailwindcss/forms 0.5.10 → 0.5.11 (minor) (#19490)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​tailwindcss/forms (0.5.10 → 0.5.11) ·
[Repo](https://github.com/tailwindlabs/tailwindcss-forms) ·
[Changelog](https://github.com/tailwindlabs/tailwindcss-forms/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/tailwindlabs/tailwindcss-forms/releases/tag/v0.5.11">0.5.11</a></h4>

<blockquote><h3 dir="auto">Fixed</h3>
<ul dir="auto">
<li>Limit attribute rules to input and select elements (<a
href="https://bounce.depfu.com/github.com/tailwindlabs/tailwindcss-forms/pull/159">#159</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="071d1c03fd...e1b609d579">See
the full diff on Github</a>. The new version differs by 3 commits:</p>
<ul>
<li><a
href="e1b609d579"><code>0.5.11</code></a></li>
<li><a
href="67ff8ea3dc"><code>Limit
attribute rules to input and select elements (#159)</code></a></li>
<li><a
href="fc3f7e6bce"><code>docs:
update installation guide to add tailwind css v4 instructions while
keeping v3 details (#180)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-24 17:20:21 -05:00
depfu[bot]
c804592719
Update bun 1.3.3 → 1.3.5 (patch) (#19489)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ bun (1.3.3 → 1.3.5) · [Repo](https://github.com/oven-sh/bun)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-24 17:20:02 -05:00
depfu[bot]
8d5e955058
Update dedent 1.7.0 → 1.7.1 (patch) (#19484)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ dedent (1.7.0 → 1.7.1) · [Repo](https://github.com/dmnd/dedent)
· [Changelog](https://github.com/dmnd/dedent/blob/main/CHANGELOG.md)
















---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-24 10:31:02 -05:00
depfu[bot]
1628713453
Update autoprefixer 10.4.22 → 10.4.23 (patch) (#19473)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ autoprefixer (10.4.22 → 10.4.23) ·
[Repo](https://github.com/postcss/autoprefixer) ·
[Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/postcss/autoprefixer/releases/tag/10.4.23">10.4.23</a></h4>

<blockquote><ul dir="auto">
<li>Reduced dependencies (by <a
href="https://bounce.depfu.com/github.com/hyperz111">@hyperz111</a>).</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let
us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="73dc62c779...212ba3c189">See
the full diff on Github</a>. The new version differs by 3 commits:</p>
<ul>
<li><a
href="212ba3c189"><code>Release
10.4.23 version</code></a></li>
<li><a
href="7f62fb6c12"><code>Update
dependencies</code></a></li>
<li><a
href="c455bb1bed"><code>chore:
inline and simplify `normalize-range` (#1539)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-22 22:59:07 -05:00
xibeiyoumian
d979daacc6
chore: fix some typos in comments (#19475)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

fix some typos in comments

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

Signed-off-by: xibeiyoumian <xibeiyoumian@outlook.com>
2025-12-22 13:09:19 +00:00
depfu[bot]
3c06c55450
Update eslint 9.39.1 → 9.39.2 (patch) (#19469)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.39.1 → 9.39.2) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.39.2">9.39.2</a></h4>

<blockquote><h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="5705833194"><code
class="notranslate">5705833</code></a> fix: warn when <code
class="notranslate">eslint-env</code> configuration comments are found
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20381">#20381</a>)
(sethamus)</li>
</ul>
<h2 dir="auto">Build Related</h2>
<ul dir="auto">
<li>
<a
href="506f1549a6"><code
class="notranslate">506f154</code></a> build: add .scss files entry to
knip (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20391">#20391</a>)
(Milos Djermanovic)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="7ca0af7f9f"><code
class="notranslate">7ca0af7</code></a> chore: upgrade to <code
class="notranslate">@eslint/js@9.39.2</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20394">#20394</a>)
(Francesco Trotta)</li>
<li>
<a
href="c43ce24ff0"><code
class="notranslate">c43ce24</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="4c9858e47b"><code
class="notranslate">4c9858e</code></a> ci: add <code
class="notranslate">v9.x-dev</code> branch (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20382">#20382</a>)
(Milos Djermanovic)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="e2772811a8...9278324aa0">See
the full diff on Github</a>. The new version differs by 7 commits:</p>
<ul>
<li><a
href="9278324aa0"><code>9.39.2</code></a></li>
<li><a
href="542266ad3c"><code>Build:
changelog update for 9.39.2</code></a></li>
<li><a
href="7ca0af7f9f"><code>chore:
upgrade to `@eslint/js@9.39.2` (#20394)</code></a></li>
<li><a
href="c43ce24ff0"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="5705833194"><code>fix:
warn when `eslint-env` configuration comments are found
(#20381)</code></a></li>
<li><a
href="506f1549a6"><code>build:
add .scss files entry to knip (#20391)</code></a></li>
<li><a
href="4c9858e47b"><code>ci:
add `v9.x-dev` branch (#20382)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-21 10:02:59 -05:00
depfu[bot]
c7036383a2
Update all of nextjs 16.0.10 → 16.1.0 (minor) (#19468)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint-config-next (16.0.10 → 16.1.0)





Sorry, we couldn't find anything useful about this release.



#### ✳️ next (16.0.10 → 16.1.0) ·
[Repo](https://github.com/vercel/next.js)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v16.1.0">16.1.0</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="581dee67e2...34916762cd">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-19 16:20:03 -05:00
Jordan Pittman
543da3f4e5
Detect utilities when containing capital letters followed by numbers (#19465)
Fixes #19463
2025-12-19 05:45:15 -05:00
depfu[bot]
dbd26b90e7
Update all of react 19.2.1 → 19.2.3 (patch) (#19464)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ react (19.2.1 → 19.2.3) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.2.3">19.2.3</a></h4>

<blockquote><h2 dir="auto">React Server Components</h2>
<ul dir="auto">
<li>Add extra loop protection to React Server Functions (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/35351">#35351</a>)</li>
</ul></blockquote>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.2.2">19.2.2</a></h4>

<blockquote><h2 dir="auto">React Server Components</h2>
<ul dir="auto">
<li>Move <code
class="notranslate">react-server-dom-webpack/*.unbundled</code> to
private <code class="notranslate">react-server-dom-unbundled</code> (<a
href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/35290">#35290</a>)</li>
<li>Patch Promise cycles and toString on Server Functions (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>,
<a
href="https://bounce.depfu.com/github.com/unstubbable">@unstubbable</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/35289">#35289</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/35345">#35345</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="053df4e856...612e371fb2">See
the full diff on Github</a>. The new version differs by 6 commits:</p>
<ul>
<li><a
href="612e371fb2"><code>Version
19.2.3</code></a></li>
<li><a
href="fc5c53c396"><code>Add
extra protection</code></a></li>
<li><a
href="b910fc15e3"><code>Version
19.2.2</code></a></li>
<li><a
href="dd0519822a"><code>Patch
Promise cycles and toString on Server Functions</code></a></li>
<li><a
href="0eeded69c1"><code>Run
CI for backport releases (#35313)</code></a></li>
<li><a
href="a4d338a6c6"><code>[test]
Cleanup stack assertions in tests mixing React Server and Client
(#35316)</code></a></li>
</ul>
</details>




#### ✳️ react-dom (19.2.1 → 19.2.3) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.2.3">19.2.3</a></h4>

<blockquote><h2 dir="auto">React Server Components</h2>
<ul dir="auto">
<li>Add extra loop protection to React Server Functions (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/35351">#35351</a>)</li>
</ul></blockquote>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.2.2">19.2.2</a></h4>

<blockquote><h2 dir="auto">React Server Components</h2>
<ul dir="auto">
<li>Move <code
class="notranslate">react-server-dom-webpack/*.unbundled</code> to
private <code class="notranslate">react-server-dom-unbundled</code> (<a
href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/35290">#35290</a>)</li>
<li>Patch Promise cycles and toString on Server Functions (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>,
<a
href="https://bounce.depfu.com/github.com/unstubbable">@unstubbable</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/35289">#35289</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/35345">#35345</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="053df4e856...612e371fb2">See
the full diff on Github</a>. The new version differs by 6 commits:</p>
<ul>
<li><a
href="612e371fb2"><code>Version
19.2.3</code></a></li>
<li><a
href="fc5c53c396"><code>Add
extra protection</code></a></li>
<li><a
href="b910fc15e3"><code>Version
19.2.2</code></a></li>
<li><a
href="dd0519822a"><code>Patch
Promise cycles and toString on Server Functions</code></a></li>
<li><a
href="0eeded69c1"><code>Run
CI for backport releases (#35313)</code></a></li>
<li><a
href="a4d338a6c6"><code>[test]
Cleanup stack assertions in tests mixing React Server and Client
(#35316)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-18 20:28:49 -05:00
depfu[bot]
d6ad8dddcf
Update enhanced-resolve 5.18.3 → 5.18.4 (patch) (#19462)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ enhanced-resolve (5.18.3 → 5.18.4) ·
[Repo](https://github.com/webpack/enhanced-resolve)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/webpack/enhanced-resolve/releases/tag/v5.18.4">5.18.4</a></h4>

<blockquote><h3 dir="auto">Fixes</h3>
<ul dir="auto">
<li>Allow subpath imports that start with <code
class="notranslate">#/</code>
</li>
<li>Handle <code class="notranslate">file:</code> schema</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="52b61d0f03...e8a6ac9fdd">See
the full diff on Github</a>. The new version differs by 6 commits:</p>
<ul>
<li><a
href="e8a6ac9fdd"><code>chore(release):
5.18.4</code></a></li>
<li><a
href="b953768580"><code>fix:
support `file:` schema (#467)</code></a></li>
<li><a
href="6ff935b236"><code>fix:
allow subpath imports that start with `#/` (#468)</code></a></li>
<li><a
href="79e2961c60"><code>fix(AliasPlugin):
correct wildcard replacement in multi-target aliases
(#465)</code></a></li>
<li><a
href="b8af8130f0"><code>fix:
fix always-false condition (#464)</code></a></li>
<li><a
href="f1bc1c2b0b"><code>fix:
note the conditions under which the request couldn&#39;t be found
(#460)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-18 15:03:51 -05:00
Jordan Pittman
219e019b24
CLI: Emit comment when source maps are saved to files (#19447)
See
https://github.com/tailwindlabs/tailwindcss/issues/19362#issuecomment-3654376948
2025-12-18 15:02:27 -05:00
Justin Wong
7fcdd84e56
Allow whitespace around @source inline() arg (#19461)
## Summary

Inspired by #19460, relaxes whitespace syntax around `@source
inline(…):`

### Before

```css
/* ❌ Error: `@source` paths must be quoted. */
@source inline( "underline" );
@source inline(
  "underline"
);
```

### After

```css
/* ✅ Generates the class names as normal. */
@source inline( "underline" );
@source inline(
  "underline"
);
```

## Test plan

Added tests to `packages/tailwindcss/src/index.test.ts`.
2025-12-18 10:51:00 -05:00
Jordan Pittman
1fbdd51a8d Update test 2025-12-17 22:27:50 -05:00
Rasso Hilber
72dea0cfea
Clarify the replaceAlpha function comment (#19457)
## Summary

Updated the comment of the `replaceAlpha` function to clarify the
function's purpose.

## Test plan

n/a
2025-12-17 22:15:42 -05:00
Rasso Hilber
dd1ca3c709
Do not wrap color-mix in a @supports rule if one already exists (#19450)
Related issue: #19445 

## Summary

Fixes an issue where tailwindcss wraps `color-mix` inside a `@supports`
block even if the original code already checks for support.

Uncompiled Code:

```css
@utility foo {
  @supports (color: color-mix(in lab, red, red)) {
    background: color-mix(in lab, var(--color-1), var(--color-2));
  }
}
```

### Compiled code: Current behavior

https://play.tailwindcss.com/KSvR7wefdh?file=css

```css
@layer utilities {
  .foo {
    @supports (color: color-mix(in lab, red, red)) {
      background: var(--color-1);
      @supports (color: color-mix(in lab, red, red)) {
        background: color-mix(in lab, var(--color-1), var(--color-2));
      }
    }
}
```

### Compiled code: Fixed behavior

```css
@layer utilities {
  @supports (color: color-mix(in lab, red, red)) {
    .foo {
      background: color-mix(in lab, var(--color-1), var(--color-2));
    }
}
```

## Test plan

I have tested this by writing a new test that was at first failing. Now
it passes.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-12-16 16:57:36 -05:00
depfu[bot]
73b5df620c
Update @types/react 19.2.6 → 19.2.7 (patch) (#19448)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react (19.2.2 → 19.2.7) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-15 23:33:16 -05:00
depfu[bot]
52e91db52c
Update @vitejs/plugin-react 5.1.1 → 5.1.2 (patch) (#19440)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (5.1.1 → 5.1.2) ·
[Repo](https://github.com/vitejs/vite-plugin-react) ·
[Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
















---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-15 09:35:08 -05:00
depfu[bot]
cb3703fc07 Update all of nextjs to version 16.0.10 2025-12-12 14:52:15 +00:00
Jordan Pittman
dd9f656274
Fix failing postcss integration test (#19436)
This is what's causing the postcss test in #19434 to fail wanted to test
this in isolation without the version bumps
2025-12-12 09:38:08 -05:00
depfu[bot]
8198819943
Update turbo 2.6.1 → 2.6.3 (patch) (#19432)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ turbo (2.6.1 → 2.6.3) ·
[Repo](https://github.com/turborepo/turbo)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-11 13:08:24 -05:00
Jordan Pittman
6518ea7f80 Update changelog 2025-12-11 11:31:02 -05:00
Jordan Pittman
6db2a6d637 Update changelog 2025-12-11 11:03:32 -05:00
Jordan Pittman
9b32f7cb2f
Release v4.1.18 (#19431) 2025-12-11 10:55:25 -05:00
Jordan Pittman
5065de4f9e Revert "Update @napi-rs/cli to version 3.5.0"
This reverts commit 3a1df5a283.
2025-12-10 11:26:51 -05:00
depfu[bot]
0a40849270 Update all of react to version 19.2.1 2025-12-10 15:53:21 +00:00
depfu[bot]
3a1df5a283 Update @napi-rs/cli to version 3.5.0 2025-12-09 10:54:19 +00:00
depfu[bot]
90abc94052
Update @napi-rs/wasm-runtime 1.0.7 → 1.1.0 (minor) (#19424)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​napi-rs/wasm-runtime (1.0.7 → 1.1.0) ·
[Repo](https://github.com/napi-rs/napi-rs)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-09 05:36:53 -05:00
Jordan Pittman
164194de06
Don’t try reading from pipes or special file descriptors (#19421)
Fixes #19420
2025-12-08 17:04:51 -05:00
Jordan Pittman
cdc851d3f8
Don't inline all env vars in Standalone CLI (#19391)
Fixes #19389

We inlined env vars in the Standalone CLI because we use some custom
patches + env vars to ensure that only the appropriate `glibc` / `musl`
binaries are included for Lightning CSS and Parcel Watcher for Linux
builds.

The build happens to run on a macOS Github CI machine though so
`NODE_PATH` was getting inlined as the string:
```
/Users/runner/work/tailwindcss/tailwindcss/node_modules/.pnpm/bun@1.3.0/node_modules/bun/bin/node_modules
```

I don't think there's a reason for `NODE_PATH` to work on the Standalone
CLI (and it didn't work because of the above bug *anyway*) so I've done
a few things here:
1. The build setup now uses `Bun.build(…)` which now supports compiling
binaries. This speeds up the build process a bit.
2. We're no longer inlining all env vars. We selectively inline only a
few using `define`.
3. I've explicitly disabled the extra `NODE_PATH` support in
`@tailwindcss/node` when building with the Standalone CLI.
4. The `__tw_readFile` hack is now gone. Async FS APIs were not
originally able to read embedded files but that changed in Bun v1.2.3
making the hack unnecessary.
5. A few more env vars are now inlined + a plugin to simplify the Oxide
loading code when bundled.
6. A plugin + env vars prevents bundling WASI build as it's not
necessary for the Standalone CLI.

I want to find a way to get rid of `__tw_resolve` and `__tw_load` but
don't want to change too much in this PR so I haven't looked into it
yet.
2025-12-08 11:23:20 -05:00
Jordan Pittman
820d90797c
Expose candidatesToAst to the language server (#19405)
This will be used to improve performance and potentially enable future
features that require generated CSS source locations.

Note: This is still 100% internal API. You can only access this via
`__unstable__loadDesignSystem` for a reason. We may chance the structure
of the arguments and/or return values as needed.
2025-12-08 10:54:42 -05:00
Jordan Pittman
478e959097
Don’t emit color-mix fallback rules inside @keyframes (#19419)
Fixes #19417
2025-12-08 09:40:16 -05:00
Adam Royle
563a016f96
Only use the last value when parsing duplicate cli arguments (#19416)
## Summary

When parsing cli args, 'mri' returns an array for duplicate args, which
causes unexpected results. All current arguments assume single values
only. Tailwind v3 works the same way.

## Test plan

Updated tests.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-12-07 12:26:22 -05:00
depfu[bot]
1ee7bb978d Update @types/bun to version 1.3.3 2025-12-06 13:42:20 +00:00
depfu[bot]
f97d094813
Update bun 1.3.2 → 1.3.3 (patch) (#19413)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ bun (1.3.2 → 1.3.3) · [Repo](https://github.com/oven-sh/bun)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-06 08:29:17 -05:00
depfu[bot]
b4f3d177fc
Update postcss-selector-parser 7.1.0 → 7.1.1 (patch) (#19408)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ postcss-selector-parser (7.1.0 → 7.1.1) ·
[Repo](https://github.com/postcss/postcss-selector-parser) ·
[Changelog](https://github.com/postcss/postcss-selector-parser/blob/master/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/postcss/postcss-selector-parser/releases/tag/v7.1.1">7.1.1</a></h4>

<blockquote><h1 dir="auto">7.1.1</h1>
<ul dir="auto">
<li>perf: replace startsWith with strict equality (<a
href="https://bounce.depfu.com/github.com/postcss/postcss-selector-parser/pull/308">#308</a>)</li>
<li>fix(types): add walkUniversal declaration (<a
href="https://bounce.depfu.com/github.com/postcss/postcss-selector-parser/pull/311">#311</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="ea6cd9324b...cf6637ed6a">See
the full diff on Github</a>. The new version differs by 6 commits:</p>
<ul>
<li><a
href="cf6637ed6a"><code>chore(release):
7.1.1</code></a></li>
<li><a
href="54cef58801"><code>fix(types):
add `walkUniversal` declaration (#311)</code></a></li>
<li><a
href="3da25fecba"><code>perf:
replace startsWith with strict equality (#308)</code></a></li>
<li><a
href="b647f7c70f"><code>ci:
test on Node.js 24 (#307)</code></a></li>
<li><a
href="337129d1e3"><code>chore:
replace abandoned pkg coveralls with coveralls-next</code></a></li>
<li><a
href="6343f29e08"><code>ci:
test on Node.js 22 (#301)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-04 10:55:11 -05:00
Andrés
9c8cf8ac19
Fix formatting of path in README.md (#19407)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

quotation mark is not closed in README doc of the postcss preprocesor

## Test plan

not needed
2025-12-04 08:25:51 -05:00
depfu[bot]
a92fa97499 Update all of nextjs to version 16.0.7 2025-12-03 20:19:40 +00:00
Jordan Pittman
a5f4644507
Validate named values in candidate parser (#19397)
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1506
2025-12-03 15:06:25 -05:00
Jordan Pittman
5f107e2822 Fix typo 2025-12-02 18:12:07 -05:00
Kirti Gautam
0d0d97d0cb
Fix invalid template tags in tests (#19400)
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-12-02 14:51:21 -05:00
depfu[bot]
7c69e83e81
Update @playwright/test 1.56.1 → 1.57.0 (minor) (#19399)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​playwright/test (1.56.1 → 1.57.0) ·
[Repo](https://github.com/Microsoft/playwright)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/microsoft/playwright/releases/tag/v1.57.0">1.57.0</a></h4>

<blockquote><h2 dir="auto">Speedboard</h2>
<p dir="auto">In HTML reporter, there's a new tab we call
"Speedboard":</p>
<a
href="https://private-user-images.githubusercontent.com/14912729/518585867-4ba117ea-ea94-4b6a-82b2-8bbd00dfe81c.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NjQxNjA4MDQsIm5iZiI6MTc2NDE2MDUwNCwicGF0aCI6Ii8xNDkxMjcyOS81MTg1ODU4NjctNGJhMTE3ZWEtZWE5NC00YjZhLTgyYjItOGJiZDAwZGZlODFjLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTExMjYlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUxMTI2VDEyMzUwNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTY4YjhlYjY1ZTA1NGU5MjU5NjU5MDQzNDdhZjMzYmEzYjRlMTg2ZGJlZTA1YTA5NWQ3ODY4MTEzNTdlNWUwNTUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.YQEEtVqnrOmR45G6zYUkuaOJL3k2DlbVb4cM0frrGQg"><img
width="600" alt="speedboard"
src="https://private-user-images.githubusercontent.com/14912729/518585867-4ba117ea-ea94-4b6a-82b2-8bbd00dfe81c.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NjQxNjA4MDQsIm5iZiI6MTc2NDE2MDUwNCwicGF0aCI6Ii8xNDkxMjcyOS81MTg1ODU4NjctNGJhMTE3ZWEtZWE5NC00YjZhLTgyYjItOGJiZDAwZGZlODFjLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTExMjYlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUxMTI2VDEyMzUwNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTY4YjhlYjY1ZTA1NGU5MjU5NjU5MDQzNDdhZjMzYmEzYjRlMTg2ZGJlZTA1YTA5NWQ3ODY4MTEzNTdlNWUwNTUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.YQEEtVqnrOmR45G6zYUkuaOJL3k2DlbVb4cM0frrGQg"
style="max-width: 100%;"></a>
<p dir="auto">It shows you all your executed tests sorted by
slowness,<br>
and can help you understand where your test suite is taking longer than
expected.<br>
Take a look at yours - maybe you'll find some tests that are spending a
longer time waiting than they should!</p>
<h2 dir="auto">Chrome for Testing</h2>
<p dir="auto">Starting with this release, Playwright switches from
Chromium, to using <a
href="https://developer.chrome.com/blog/chrome-for-testing/">Chrome for
Testing</a> builds. Both headed and headless browsers are subject to
this. Your tests should still be passing after upgrading to Playwright
1.57.</p>
<p dir="auto">We're expecting no functional changes to come from this
switch. The biggest change is the new icon and title in your
toolbar.</p>
<a
href="https://private-user-images.githubusercontent.com/14912729/518586000-e9a5c4f2-9f35-4c27-9382-0f5eda377097.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NjQxNjA4MDQsIm5iZiI6MTc2NDE2MDUwNCwicGF0aCI6Ii8xNDkxMjcyOS81MTg1ODYwMDAtZTlhNWM0ZjItOWYzNS00YzI3LTkzODItMGY1ZWRhMzc3MDk3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTExMjYlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUxMTI2VDEyMzUwNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWQ3YWFkMTAzM2I0YmQ1MDU4ODRlMDA0YzQ2NjQ3YjRlZjJkYTA4MmY5YTdlYzBmZmE1ZWI4NDk0NzQ2YTdiMGMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.IPnNJmILND-iF20WnvLwMZQZMteYB8WFNIPXNL-lofc"><img
width="500" alt="new and old logo"
src="https://private-user-images.githubusercontent.com/14912729/518586000-e9a5c4f2-9f35-4c27-9382-0f5eda377097.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NjQxNjA4MDQsIm5iZiI6MTc2NDE2MDUwNCwicGF0aCI6Ii8xNDkxMjcyOS81MTg1ODYwMDAtZTlhNWM0ZjItOWYzNS00YzI3LTkzODItMGY1ZWRhMzc3MDk3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTExMjYlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUxMTI2VDEyMzUwNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWQ3YWFkMTAzM2I0YmQ1MDU4ODRlMDA0YzQ2NjQ3YjRlZjJkYTA4MmY5YTdlYzBmZmE1ZWI4NDk0NzQ2YTdiMGMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.IPnNJmILND-iF20WnvLwMZQZMteYB8WFNIPXNL-lofc"
style="max-width: 100%;"></a>
<p dir="auto">If you still see an unexpected behaviour change, please <a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/new">file
an issue</a>.</p>
<p dir="auto">On Arm64 Linux, Playwright continues to use Chromium.</p>
<h2 dir="auto">Waiting for webserver output</h2>
<p dir="auto"><a
href="https://playwright.dev/docs/api/class-testconfig#test-config-web-server">testConfig.webServer</a>
added a <code class="notranslate">wait</code> field. Pass a regular
expression, and Playwright will wait until the webserver logs match
it.</p>
<div class="highlight highlight-source-js" dir="auto"><pre
class="notranslate"><span class="pl-k">import</span> <span
class="pl-kos">{</span> <span class="pl-s1">defineConfig</span> <span
class="pl-kos">}</span> <span class="pl-k">from</span> <span
class="pl-s">'@playwright/test'</span><span class="pl-kos">;</span>

<span class="pl-k">export</span> <span class="pl-k">default</span> <span
class="pl-en">defineConfig</span><span class="pl-kos">(</span><span
class="pl-kos">{</span>
  <span class="pl-c1">webServer</span>: <span class="pl-kos">{</span>
<span class="pl-c1">command</span>: <span class="pl-s">'npm run
start'</span><span class="pl-kos">,</span>
    <span class="pl-c1">wait</span>: <span class="pl-kos">{</span>
<span class="pl-c1">stdout</span>: <span class="pl-s">'/Listening on
port (?&lt;my_server_port&gt;\\d+)/'</span>
    <span class="pl-kos">}</span><span class="pl-kos">,</span>
  <span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span
class="pl-kos">;</span></pre></div>
<p dir="auto">If you include a named capture group into the expression,
then Playwright will provide the capture group contents via environment
variables:</p>
<div class="highlight highlight-source-js" dir="auto"><pre
class="notranslate"><span class="pl-k">import</span> <span
class="pl-kos">{</span> <span class="pl-s1">test</span><span
class="pl-kos">,</span> <span class="pl-s1">expect</span> <span
class="pl-kos">}</span> <span class="pl-k">from</span> <span
class="pl-s">'@playwright/test'</span><span class="pl-kos">;</span>

<span class="pl-s1">test</span><span class="pl-kos">.</span><span
class="pl-en">use</span><span class="pl-kos">(</span><span
class="pl-kos">{</span> <span class="pl-c1">baseUrl</span>: <span
class="pl-s">`http://localhost:<span class="pl-s1"><span
class="pl-kos">${</span><span class="pl-s1">process</span><span
class="pl-kos">.</span><span class="pl-c1">env</span><span
class="pl-kos">.</span><span class="pl-c1">MY_SERVER_PORT</span> <span
class="pl-c1">??</span> <span class="pl-c1">3000</span><span
class="pl-kos">}</span></span>`</span> <span
class="pl-kos">}</span><span class="pl-kos">)</span><span
class="pl-kos">;</span>

<span class="pl-en">test</span><span class="pl-kos">(</span><span
class="pl-s">'homepage'</span><span class="pl-kos">,</span> <span
class="pl-k">async</span> <span class="pl-kos">(</span><span
class="pl-kos">{</span> page <span class="pl-kos">}</span><span
class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span
class="pl-kos">{</span>
<span class="pl-k">await</span> <span class="pl-s1">page</span><span
class="pl-kos">.</span><span class="pl-en">goto</span><span
class="pl-kos">(</span><span class="pl-s">'/'</span><span
class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span
class="pl-kos">;</span></pre></div>
<p dir="auto">This is not just useful for capturing varying ports of dev
servers. You can also use it to wait for readiness of a service that
doesn't expose an HTTP readiness check, but instead prints a readiness
message to stdout or stderr.</p>
<h2 dir="auto">Breaking Change</h2>
<p dir="auto">After 3 years of being deprecated, we removed <code
class="notranslate">Page#accessibility</code> from our API. Please use
other libraries such as <a href="https://www.deque.com/axe/">Axe</a> if
you need to test page accessibility. See our Node.js <a
href="https://playwright.dev/docs/accessibility-testing">guide</a> for
integration with Axe.</p>
<h2 dir="auto">New APIs</h2>
<ul dir="auto">
<li>New property <a
href="https://playwright.dev/docs/api/class-testconfig#test-config-tag">testConfig.tag</a>
adds a tag to all tests in this run. This is useful when using <a
href="https://playwright.dev/docs/test-sharding#merging-reports-from-multiple-shards">merge-reports</a>.</li>
<li>
<a
href="https://playwright.dev/docs/api/class-worker#worker-event-console">worker.on('console')</a>
event is emitted when JavaScript within the worker calls one of console
API methods, e.g. console.log or console.dir. <a
href="https://playwright.dev/docs/api/class-worker#worker-wait-for-event">worker.waitForEvent()</a>
can be used to wait for it.</li>
<li>
<a
href="https://playwright.dev/docs/api/class-locator#locator-description">locator.description()</a>
returns locator description previously set with <a
href="https://playwright.dev/docs/api/class-locator#locator-describe">locator.describe()</a>,
and <code class="notranslate">Locator.toString()</code> now uses the
description when available.</li>
<li>New option <a
href="https://playwright.dev/docs/api/class-locator#locator-click-option-steps"><code
class="notranslate">steps</code></a> in <a
href="https://playwright.dev/docs/api/class-locator#locator-click">locator.click()</a>
and <a
href="https://playwright.dev/docs/api/class-locator#locator-drag-to">locator.dragTo()</a>
that configures the number of <code class="notranslate">mousemove</code>
events emitted while moving the mouse pointer to the target
element.</li>
<li>Network requests issued by <a
href="https://playwright.dev/docs/service-workers#network-events-and-routing">Service
Workers</a> are now reported and can be routed through the <a
href="https://playwright.dev/docs/api/class-browsercontext">BrowserContext</a>,
only in Chromium. You can opt out using the <code
class="notranslate">PLAYWRIGHT_DISABLE_SERVICE_WORKER_NETWORK</code>
environment variable.</li>
<li>Console messages from Service Workers are dispatched through <a
href="https://playwright.dev/docs/api/class-worker#worker-event-console">worker.on('console')</a>.
You can opt out of this using the <code
class="notranslate">PLAYWRIGHT_DISABLE_SERVICE_WORKER_CONSOLE</code>
environment variable.</li>
</ul>
<h2 dir="auto">Browser Versions</h2>
<ul dir="auto">
<li>Chromium 143.0.7499.4</li>
<li>Mozilla Firefox 142.0.1</li>
<li>WebKit 26.0</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="54c711571a...8058197258">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-12-02 07:09:28 -05:00
Robin Malfait
229121dd14
Canonicalization: combine text-* and leading-* classes (#19396)
This PR improves the canonicalization when using `text-*` and
`leading-*` utilities together.

When using classes such as:
```html
<div class="text-sm leading-7"></div>
```

Then the canonical way of writing this is:
```html
<div class="text-sm/7"></div>
```

Similarly, if you already have a modifier applied, and add a new
line-height utility. It will also combine them into the canonical form:
```html
<div class="text-sm/6 leading-7"></div>
```
becomes:
```html
<div class="text-sm/7"></div>
```

This is because the final CSS output of `text-sm/6 leading-7` is:
```css
/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */
.text-sm\/6 {
  font-size: var(--text-sm, 0.875rem);
  line-height: calc(var(--spacing, 0.25rem) * 6);
}
.leading-7 {
  --tw-leading: calc(var(--spacing, 0.25rem) * 7);
  line-height: calc(var(--spacing, 0.25rem) * 7);
}
@property --tw-leading {
  syntax: "*";
  inherits: false;
}
```

Where the `line-height` of the `leading-7` class wins over the
`line-height` of the `text-sm/6` class.

### Implementation

#### On the fly pre-computation

Right now, we are not using any AST based transformations yet and
instead rely on a pre-computed list. However, with arbitrary values we
don't have pre-computed values for `text-sm/123` for example.

What we do instead is if we see a utility that sets `line-height` and
other utilities set `font-size` then we pre-compute those computations
on the fly.

We will prefer named font-sizes (such as `sm`, `lg`, etc). We will also
prefer bare values for line-height (such as `7`) over arbitrary values
(such as `[123px]`).

#### Canonicalization of the CSS AST

Another thing we had to do is to make sure that when multiple
declarations of the same property exist, that we only keep the last one.
In the real world, multiple declarations of the same value is typically
used for fallback values (e.g.: `background-color: #fff;
background-color: oklab(255 255 255 / 1);`).

But for our use case, I believe we can safely remove the earlier
declarations to make the most modern and thus the last declaration win.

#### Trying combinations based on `property` only

One small change we had to make is that we try combinations of utilities
based on property only instead of property _and_ value. This is
important for cases such as `text-sm/6 leading-7`. These 2 classes will
set a `lin-height` of `24px` and `28px` respectively so they will never
match.

However, once combined together, there will be 2 line-height values, and
the last one wins. The signature of `text-sm/6 leading-7` becomes:
```css
.x {
  font-size: 14px;          /* From text-sm/6 */
  line-height: 24px;        /* From text-sm/6 */
  line-height: 28px;        /* From leading-7 */
}
```

↓↓↓↓↓↓↓↓↓

```css
.x {
  font-size: 14px;          /* From text-sm/6 */
  line-height: 28px;        /* From leading-7 */
}
```

This now shows that just `text-sm/7` is the canonical form. Because it
produces the same final CSS output.


## Test plan

1. All existing tests pass
2. Added a bunch of new tests where we combine `text-*` and `leading-*`
utilities with named, bare and arbitrary values. Even with existing
modifiers on the text utilities.

<img width="1010" height="1099" alt="image"
src="https://github.com/user-attachments/assets/d2775692-a442-4604-8371-21dacf16ebfc"
/>
2025-12-01 16:01:24 +01:00
Jordan Pittman
243615e3f2
Handle backwards compatibility for content theme from JS configs (#19381)
Fixes #19343

This PR makes it so the `content-*` utilities read from the
`--content-*` theme namespace. This change is **purely for backwards
compatibility with Tailwind CSS v3**. It is recommended you use
arbitrary values with the `content-*` utility instead.
2025-11-29 11:19:37 -05:00
Jordan Pittman
764275143e
Improve compatibility with special default values in JS configs (#19348)
Fixes #19345

In v3 the `ringColor.DEFAULT` option was used as the default color for
ring utilities (when it was defined). This currently gets translated as
`--ring-color` but that doesn't work this way in v4. Instead it should
translate to `--default-ring-color` and *not* `--ring-color`.

I've also tweaked the upgrade tool to handle this properly as well.
2025-11-28 15:45:10 -05:00
Robin Malfait
af481175e7
remove unnecessary intermediate check 2025-11-26 12:34:42 +01:00
Robin Malfait
9e436f7751
Try to canonicalize any arbitrary utility to a bare value (#19379)
This PR adds an improvement to our canonicalization logic when dealing
with arbitrary values. When trying to canonicalize utilities, we make
use of the intellisense suggestions list where we typically use
multiples of the spacing scale.

This means that a value like `gap-[128px]` gets properly canonicalized
to `gap-32`. However, when you try a value that we typically don't
suggest such as `gap-[116px]` then it doesn't get canonicalized at all.

This PR fixes that by trying to use the spacing scale and convert `116px
/ 4px` and try the `gap-29` utility instead.

This is done by canonicalizing the incoming arbitrary value and the
spacing multipliers such that `--spacing: 0.25rem` and `--spacing: 4px`
both work as expected.

### Test plan

1. Added some tests with a spacing scale of `0.25rem` (which is the
default)
2. Added some tests with the same spacing scale in a different unit
`4px`
3. Added some tests with a different spacing scale `1px`

Also had to update 1 test that now gets canonicalized properly, e.g.:
`w-[124px]` → `w-31`.
2025-11-26 11:07:21 +00:00
Jordan Pittman
117433a100 Revert "Update globby 15.0.0 → 16.0.0 (major) (#19357)"
This reverts commit 28670a9b91.
2025-11-25 13:13:08 -05:00
Jordan Pittman
0e8f075ca2
Fix source map generation during when watching files on the CLI (#19373)
Fixes #19362

We were overwriting the source map with the "decoded" map returned by
the compiler but didn't wrap it in the helper intended to help inline vs
file maps. This resulted in two issues:
1. `undefined` being appended to the CSS file when using `--map`
2. `undefined` being passed to `writeFile(…)` when using `--map <file>`

This PR fixes both.
2025-11-25 12:22:57 -05:00
depfu[bot]
28670a9b91
Update globby 15.0.0 → 16.0.0 (major) (#19357)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ globby (15.0.0 → 16.0.0) ·
[Repo](https://github.com/sindresorhus/globby)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/sindresorhus/globby/releases/tag/v16.0.0">16.0.0</a></h4>

<blockquote><h3 dir="auto">Breaking</h3>
<ul dir="auto">
<li>Respect parent .gitignore files when <code
class="notranslate">gitignore</code> option is enabled <a
href="b9a9a84c76"><tt>b9a9a84</tt></a>
<ul dir="auto">
<li>When <code class="notranslate">{gitignore: true}</code>, Globby now
searches for .gitignore files up to the Git repo root (if detected).
This matches Git's actual behavior where parent .gitignore patterns
apply to subdirectories. Previously, only .gitignore files within the
glob search path were considered. This change could break existing code
that relied on the previous, more limited behavior.</li>
</ul>
</li>
</ul>
<h3 dir="auto">Improvements</h3>
<ul dir="auto">
<li>Support negation-only patterns <a
href="1273541cca"><tt>1273541</tt></a>
</li>
<li>Respect custom fs implementation from options <a
href="c2eb272520"><tt>c2eb272</tt></a>
</li>
<li>Add some more options to <code
class="notranslate">isGitIgnored</code> functions <a
href="e794ff905a"><tt>e794ff9</tt></a>
</li>
<li>Improve error message for invalid <code
class="notranslate">cwd</code> option <a
href="7535e6d74c"><tt>7535e6d</tt></a>
</li>
</ul>
<h3 dir="auto">Fixes</h3>
<ul dir="auto">
<li>Fix <code class="notranslate">{gitignore: true}</code> performance
issue <a
href="fcb07c3907"><tt>fcb07c3</tt></a>
</li>
<li>Fix bundler compatibility <a
href="e6c72c9ff8"><tt>e6c72c9</tt></a>
</li>
<li>Fix parent directory patterns with <code
class="notranslate">gitignore</code> option <a
href="11dd2dfae3"><tt>11dd2df</tt></a>
</li>
<li>Fix ignore patterns with parent directory references <a
href="a58d117b0e"><tt>a58d117</tt></a>
</li>
<li>Fix types for <code class="notranslate">stats</code> option <a
href="c10f600c61"><tt>c10f600</tt></a>
</li>
</ul>
<hr>
<p dir="auto"><a
href="https://bounce.depfu.com/github.com/sindresorhus/globby/compare/v15.0.0...v16.0.0"><tt>v15.0.0...v16.0.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="b65747ec17...cdbb6a2e66">See
the full diff on Github</a>. The new version differs by 22 commits:</p>
<ul>
<li><a
href="cdbb6a2e66"><code>16.0.0</code></a></li>
<li><a
href="e6c72c9ff8"><code>Fix
bundler compatibility</code></a></li>
<li><a
href="b9a9a84c76"><code>Respect
parent .gitignore files when `gitignore` option is
enabled</code></a></li>
<li><a
href="db9cb722a0"><code>Add
test for `suppressErrors` option with file patterns</code></a></li>
<li><a
href="69e0ee7bbe"><code>Add
test for ignore option with trailing slashes</code></a></li>
<li><a
href="11dd2dfae3"><code>Fix
parent directory patterns with `gitignore` option</code></a></li>
<li><a
href="e4d09180fb"><code>Add
tests for special characters in directory names</code></a></li>
<li><a
href="f92613260e"><code>Improve
path check in gitignore logic</code></a></li>
<li><a
href="7535e6d74c"><code>Improve
error message for invalid `cwd` option</code></a></li>
<li><a
href="c10f600c61"><code>Fix
types for `stats` option</code></a></li>
<li><a
href="a58d117b0e"><code>Fix
ignore patterns with parent directory references</code></a></li>
<li><a
href="40022f3927"><code>Improve
Windows path documentation</code></a></li>
<li><a
href="1273541cca"><code>Support
negation-only patterns</code></a></li>
<li><a
href="c2eb272520"><code>Respect
custom fs implementation from options</code></a></li>
<li><a
href="aa694e0847"><code>Improve
docs for `convertPathToPattern`</code></a></li>
<li><a
href="306a82ddc7"><code>Add
test for .npmignore in subdirectories</code></a></li>
<li><a
href="9370b3574e"><code>Clarify
`gitignore` priority in documentation</code></a></li>
<li><a
href="c743dad53b"><code>Add
test for nested gitignore with negation</code></a></li>
<li><a
href="e794ff905a"><code>Add
some more options to isGitIgnored functions</code></a></li>
<li><a
href="fcb07c3907"><code>Fix
`{gitignore: true}` performance issue</code></a></li>
<li><a
href="2dee4320b6"><code>Add
tests for some fast-glob limitations</code></a></li>
<li><a
href="6da24d3e2b"><code>Add
test for gitignore patterns starting with `./` or `../`</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-11-25 09:27:57 -05:00
depfu[bot]
a1f533a0e0 Update @emnapi/core to version 1.7.1 2025-11-21 14:57:54 +00:00
depfu[bot]
385edfb80d
Update emnapi 1.6.0 → 1.7.1 (minor) (#19351)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ emnapi (1.6.0 → 1.7.1) ·
[Repo](https://github.com/toyobayashi/emnapi)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/toyobayashi/emnapi/releases/tag/v1.7.1">1.7.1</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>move Node-API version detection by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/182">#182</a>
</li>
<li>feat: support SharedArrayBuffer in napi_create_dataview by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/183">#183</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/compare/v1.7.0...v1.7.1"><tt>v1.7.0...v1.7.1</tt></a></p></blockquote>
<h4><a
href="https://github.com/toyobayashi/emnapi/releases/tag/v1.7.0">1.7.0</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>feat: add napi_create_object_with_properties method by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/181">#181</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/compare/v1.6.0...v1.7.0"><tt>v1.6.0...v1.7.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="86d68f88ec...f40577ad60">See
the full diff on Github</a>. The new version differs by 6 commits:</p>
<ul>
<li><a
href="f40577ad60"><code>1.7.1</code></a></li>
<li><a
href="c95f961fbc"><code>feat:
support SharedArrayBuffer in napi_create_dataview (#183)</code></a></li>
<li><a
href="92964e3738"><code>move
Node-API version detection (#182)</code></a></li>
<li><a
href="11f4936f39"><code>1.7.0</code></a></li>
<li><a
href="44bd3c798d"><code>[Backport]
feat: add napi_create_object_with_properties method
(#181)</code></a></li>
<li><a
href="62ab842b64"><code>ci:
fix version retrieval</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-11-21 09:41:20 -05:00
depfu[bot]
251fe27658
Update @vitejs/plugin-react 5.1.0 → 5.1.1 (patch) (#19355)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (5.1.0 → 5.1.1) ·
[Repo](https://github.com/vitejs/vite-plugin-react) ·
[Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)



<details>
<summary>Release Notes</summary>

<h4>5.1.1 (from changelog)</h4>
<blockquote><h3 dir="auto">Update code to support newer <code
class="notranslate">rolldown-vite</code> (<a
href="https://bounce.depfu.com/github.com/vitejs/vite-plugin-react/pull/976">#976</a>)</h3>
<p dir="auto"><code class="notranslate">rolldown-vite</code> will remove
<code class="notranslate">optimizeDeps.rollupOptions</code> in favor of
<code class="notranslate">optimizeDeps.rolldownOptions</code> soon. This
plugin now uses <code
class="notranslate">optimizeDeps.rolldownOptions</code> to support newer
<code class="notranslate">rolldown-vite</code>. Please update <code
class="notranslate">rolldown-vite</code> to the latest version if you
are using an older version.</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>













---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-11-21 09:41:12 -05:00
depfu[bot]
98d221f24b
Update @emnapi/runtime 1.6.0 → 1.7.1 (minor) (#19349)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​emnapi/runtime (1.6.0 → 1.7.1) ·
[Repo](https://github.com/toyobayashi/emnapi)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/toyobayashi/emnapi/releases/tag/v1.7.1">1.7.1</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>move Node-API version detection by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/182">#182</a>
</li>
<li>feat: support SharedArrayBuffer in napi_create_dataview by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/183">#183</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/compare/v1.7.0...v1.7.1"><tt>v1.7.0...v1.7.1</tt></a></p></blockquote>
<h4><a
href="https://github.com/toyobayashi/emnapi/releases/tag/v1.7.0">1.7.0</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>feat: add napi_create_object_with_properties method by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/181">#181</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/compare/v1.6.0...v1.7.0"><tt>v1.6.0...v1.7.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="86d68f88ec...f40577ad60">See
the full diff on Github</a>. The new version differs by 6 commits:</p>
<ul>
<li><a
href="f40577ad60"><code>1.7.1</code></a></li>
<li><a
href="c95f961fbc"><code>feat:
support SharedArrayBuffer in napi_create_dataview (#183)</code></a></li>
<li><a
href="92964e3738"><code>move
Node-API version detection (#182)</code></a></li>
<li><a
href="11f4936f39"><code>1.7.0</code></a></li>
<li><a
href="44bd3c798d"><code>[Backport]
feat: add napi_create_object_with_properties method
(#181)</code></a></li>
<li><a
href="62ab842b64"><code>ci:
fix version retrieval</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-11-21 09:40:51 -05:00
Robin Malfait
479b725cd3
Bump Vitest to v4 (#19216)
This PR bumps Vitest from v2 to v4. As far as I know we don't use any
Vitest specific features in our tests, but had to upgrade the
`vitest.workspace.ts` file to a `vitest.config.ts` file instead.

The only features we use are the typical `describe`, `it`, `test`, and
`expect` functions.

The only other part we use is `vi.spyOn` and `vi.fn` but those didn't
change in API either.

The test shards were removed to prevent errors. Not all suites have
enough files / tests to be broken up into 3 parts so Vitest now errors
when that happens.

### Test plan

1. All tests should pass in CI.
2. All integration tests should pass in CI.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-11-20 18:16:20 -05:00
Jordan Pittman
642b9b8576
Don’t unconditionally convert config keys to kebab-case (#19337)
Fixes #18114
Closes #18115

This PR changes JS config handling such that we always preserve casing
for theme keys (when possible).

Now there are *two* exceptions to this rule:

1. Top-level keys in the theme *do* get converted to kebab-case. As CSS
variables are not generated from these this shouldn't be a big issue.

All of our internal plugins look for kebab-case keys. So, for example,
take the path `backgroundColor.red.500`. This must translate to
`--background-color-red-500`. But if you had something like
`backgroundColor.lightBlue` it would be perfectly fine for that to
translate to `--background-color-lightBlue` internally (and thus the
utility be written as `bg-lightBlue`.

2. Tuple object keys are converted to kebab-case as well.

These keys are converted to "nested" key syntax internally and
typtically represent CSS property names.

For example:
```js
export default {
  theme: {
    fontSize: {
      xs: ["1.5rem", { lineHeight: "1.3" }]
    },
    
    fontFamily: {
      sans: ["Potato Mono", { fontVariationSettings: '"XHGT" 0.7' }]
    }
  }
}
```

The `lineHeight` key here must be converted to `line-height` because it
represents a CSS property name. The theme key that represents this value
is `--text-xs--line-height`. The same situation applies for the
`fontVariationSettings` where the theme key is
`--font-sans--font-variation-settings`.
2025-11-20 22:39:55 +00:00
Jordan Pittman
5a8e878319
Handle future and experimental config keys during upgrade (#19344)
Fixes #19342
2025-11-20 22:11:43 +00:00
depfu[bot]
68337df3db Update @types/bun to version 1.3.2 2025-11-20 22:08:27 +00:00
depfu[bot]
7c0eeaa3a6
Update all of nextjs 15.5.6 → 16.0.3 (major) (#19341)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint-config-next (15.5.6 → 16.0.3)





Sorry, we couldn't find anything useful about this release.



#### ✳️ next (15.5.6 → 16.0.3) ·
[Repo](https://github.com/vercel/next.js)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v16.0.3">16.0.3</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v16.0.2">16.0.2</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v16.0.1">16.0.1</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v16.0.0">16.0.0</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="01014d476c...2eda33ce84">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

---------

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-11-20 10:54:23 -05:00
depfu[bot]
172c663bd6
Update tsup 8.5.0 → 8.5.1 (patch) (#19338)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ tsup (8.5.0 → 8.5.1)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-11-19 18:18:45 -05:00
depfu[bot]
3d899f73f8 Update @types/react-dom to version 19.2.3 2025-11-19 10:43:12 +00:00
depfu[bot]
5f243dbb0f
Update bun 1.3.0 → 1.3.2 (patch) (#19334) 2025-11-19 11:25:28 +01:00
Jordan Pittman
a7c968efb1
bump turbo (#19179)
The Bun build issue was caused by Turborepo passing through
`USERPROFILE` from the env. Probably need to file a bug with Bun:

```
Works:
env: USERPROFILE: undefined
CWD: `D:\a\tailwindcss\tailwindcss\packages\@tailwindcss-standalone`

Fails:
env: USERPROFILE: "C:\Users\runneradmin"
CWD: `D:\a\tailwindcss\tailwindcss\packages\@tailwindcss-standalone`
```
2025-11-18 15:14:24 -05:00
Jordan Pittman
e4b6c67bcb
Support Vite Environment API (#18970)
Fixes #18002

Very much a work in progress b/c I don't (yet) understand how the newer
APIs are intended to function.

- [x] Needs env specific tests that verify the environment API is being
used
2025-11-18 12:38:57 -05:00
depfu[bot]
c532955dd1
Update autoprefixer 10.4.21 → 10.4.22 (patch) (#19322)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ autoprefixer (10.4.21 → 10.4.22) ·
[Repo](https://github.com/postcss/autoprefixer) ·
[Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/postcss/autoprefixer/releases/tag/10.4.22">10.4.22</a></h4>

<blockquote><ul dir="auto">
<li>Fixed <code class="notranslate">stretch</code> prefixes on new Can I
Use database.</li>
<li>Updated <code class="notranslate">fraction.js</code>.</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let
us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="541295c0e6...73dc62c779">See
the full diff on Github</a>. The new version differs by 6 commits:</p>
<ul>
<li><a
href="73dc62c779"><code>Release
10.4.22 version</code></a></li>
<li><a
href="9973c59f48"><code>Lock
CI action versions</code></a></li>
<li><a
href="4b4feca71a"><code>Fix
Node.js 10 on CI</code></a></li>
<li><a
href="15c21d3a7c"><code>Fix
old Node.js CI</code></a></li>
<li><a
href="27523c1c56"><code>Update
fraction.js</code></a></li>
<li><a
href="88a0d3e0f8"><code>Update
dependencies and fix stretch and update example</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-11-17 17:22:37 -05:00
Alexander Lichter
21f2bf99b9
perf(vite): add plugin hook filter (#19308)
## Summary

This PR adds plugin [hook
filters](https://vite.dev/guide/api-plugin#hook-filters) to the Vite
plugin. They are backwards-compatible, having no impact on older Vite
versions as the check for `isPotentialCssRootFile` is still included.

It can be dropped once you don't need to support older Vite versions.

## See also

https://github.com/e18e/ecosystem-issues/issues/171

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-11-14 10:44:07 -05:00
Jordan Pittman
4455051c48
Skip comments in Ruby files when checking for class names (#19243)
Fixes #19239
2025-11-13 05:53:39 -05:00
Ishita SIngh
5bc90dd2e0
Include filename and line numbers in CSS parse errors (#19282)
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-11-10 13:29:23 -05:00
depfu[bot]
5a89571efd
Update eslint 9.37.0 → 9.39.1 (minor) (#19287)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.37.0 → 9.39.1) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.39.1">9.39.1</a></h4>

<blockquote><h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="650753ee39"><code
class="notranslate">650753e</code></a> fix: Only pass node to JS lang
visitor methods (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20283">#20283</a>)
(Nicholas C. Zakas)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="51b51f4f1c"><code
class="notranslate">51b51f4</code></a> docs: add a section on when to
use extends vs cascading (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20268">#20268</a>)
(Tanuj Kanti)</li>
<li>
<a
href="b44d42699d"><code
class="notranslate">b44d426</code></a> docs: Update README (GitHub
Actions Bot)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="92db329211"><code
class="notranslate">92db329</code></a> chore: update <code
class="notranslate">@eslint/js</code> version to 9.39.1 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20284">#20284</a>)
(Francesco Trotta)</li>
<li>
<a
href="c7ebefc9ea"><code
class="notranslate">c7ebefc</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="61778f6ca3"><code
class="notranslate">61778f6</code></a> chore: update
eslint-config-eslint dependency @eslint/js to ^9.39.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20275">#20275</a>)
(renovate[bot])</li>
<li>
<a
href="d9ca2fcd9a"><code
class="notranslate">d9ca2fc</code></a> ci: Add rangeStrategy to eslint
group in renovate config (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20266">#20266</a>)
(唯然)</li>
<li>
<a
href="009e5076ff"><code
class="notranslate">009e507</code></a> test: fix version tests for
ESLint v10 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20274">#20274</a>)
(Milos Djermanovic)</li>
</ul></blockquote>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.39.0">9.39.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="cc57d87a3f"><code
class="notranslate">cc57d87</code></a> feat: update error loc to key in
<code class="notranslate">no-dupe-class-members</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20259">#20259</a>)
(Tanuj Kanti)</li>
<li>
<a
href="126552fcf3"><code
class="notranslate">126552f</code></a> feat: update error location in
<code class="notranslate">for-direction</code> and <code
class="notranslate">no-dupe-args</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20258">#20258</a>)
(Tanuj Kanti)</li>
<li>
<a
href="167d0970d3"><code
class="notranslate">167d097</code></a> feat: update <code
class="notranslate">complexity</code> rule to highlight only static
block header (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20245">#20245</a>)
(jaymarvelz)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="15f5c7c168"><code
class="notranslate">15f5c7c</code></a> fix: forward traversal <code
class="notranslate">step.args</code> to visitors (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20253">#20253</a>)
(jaymarvelz)</li>
<li>
<a
href="5a1a534e87"><code
class="notranslate">5a1a534</code></a> fix: allow JSDoc comments in
object-shorthand rule (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20167">#20167</a>)
(Nitin Kumar)</li>
<li>
<a
href="e86b813eb6"><code
class="notranslate">e86b813</code></a> fix: Use more types from
@eslint/core (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20257">#20257</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="927272d1f0"><code
class="notranslate">927272d</code></a> fix: correct <code
class="notranslate">Scope</code> typings (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20198">#20198</a>)
(jaymarvelz)</li>
<li>
<a
href="37f76d9c53"><code
class="notranslate">37f76d9</code></a> fix: use <code
class="notranslate">AST.Program</code> type for Program node (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20244">#20244</a>)
(Francesco Trotta)</li>
<li>
<a
href="ae07f0b333"><code
class="notranslate">ae07f0b</code></a> fix: unify timing report for
concurrent linting (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20188">#20188</a>)
(jaymarvelz)</li>
<li>
<a
href="b165d471be"><code
class="notranslate">b165d47</code></a> fix: correct <code
class="notranslate">Rule</code> typings (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20199">#20199</a>)
(jaymarvelz)</li>
<li>
<a
href="fb97cda70d"><code
class="notranslate">fb97cda</code></a> fix: improve error message for
missing fix function in suggestions (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20218">#20218</a>)
(jaymarvelz)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="d3e81e30ee"><code
class="notranslate">d3e81e3</code></a> docs: Always recommend to include
a files property (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20158">#20158</a>)
(Percy Ma)</li>
<li>
<a
href="0f0385f140"><code
class="notranslate">0f0385f</code></a> docs: use consistent naming
recommendation (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20250">#20250</a>)
(Alex M. Spieslechner)</li>
<li>
<a
href="a3b145609a"><code
class="notranslate">a3b1456</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="cf5f2dd58d"><code
class="notranslate">cf5f2dd</code></a> docs: fix correct tag of <code
class="notranslate">no-useless-constructor</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20255">#20255</a>)
(Tanuj Kanti)</li>
<li>
<a
href="10b995c8e5"><code
class="notranslate">10b995c</code></a> docs: add TS options and examples
for <code class="notranslate">nofunc</code> in <code
class="notranslate">no-use-before-define</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20249">#20249</a>)
(Tanuj Kanti)</li>
<li>
<a
href="2584187e4a"><code
class="notranslate">2584187</code></a> docs: remove repetitive word in
comment (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20242">#20242</a>)
(reddaisyy)</li>
<li>
<a
href="637216bd4f"><code
class="notranslate">637216b</code></a> docs: update CLI flags migration
instructions (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20238">#20238</a>)
(jaymarvelz)</li>
<li>
<a
href="e7cda3bdf1"><code
class="notranslate">e7cda3b</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="7b9446f7cc"><code
class="notranslate">7b9446f</code></a> docs: handle empty flags sections
on the feature flags page (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20222">#20222</a>)
(sethamus)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="dfe3c1b203"><code
class="notranslate">dfe3c1b</code></a> chore: update <code
class="notranslate">@eslint/js</code> version to 9.39.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20270">#20270</a>)
(Francesco Trotta)</li>
<li>
<a
href="2375a6de82"><code
class="notranslate">2375a6d</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="a1f4e52d67"><code
class="notranslate">a1f4e52</code></a> chore: update <code
class="notranslate">@eslint</code> dependencies (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20265">#20265</a>)
(Francesco Trotta)</li>
<li>
<a
href="c7d3229848"><code
class="notranslate">c7d3229</code></a> chore: update dependency
@eslint/core to ^0.17.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20256">#20256</a>)
(renovate[bot])</li>
<li>
<a
href="27549bc774"><code
class="notranslate">27549bc</code></a> chore: update fuzz testing to not
error if code sample minimizer fails (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20252">#20252</a>)
(Milos Djermanovic)</li>
<li>
<a
href="a1370ee40e"><code
class="notranslate">a1370ee</code></a> ci: bump actions/setup-node from
5 to 6 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20230">#20230</a>)
(dependabot[bot])</li>
<li>
<a
href="9e7fad4a18"><code
class="notranslate">9e7fad4</code></a> chore: add script to
auto-generate eslint:recommended configuration (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20208">#20208</a>)
(唯然)</li>
</ul></blockquote>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.38.0">9.38.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="ce40f74efd"><code
class="notranslate">ce40f74</code></a> feat: update <code
class="notranslate">complexity</code> rule to only highlight function
header (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20048">#20048</a>)
(Atul Nair)</li>
<li>
<a
href="e37e590aae"><code
class="notranslate">e37e590</code></a> feat: correct <code
class="notranslate">no-loss-of-precision</code> false positives with
<code class="notranslate">e</code> notation (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20187">#20187</a>)
(Francesco Trotta)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="50c3dfd980"><code
class="notranslate">50c3dfd</code></a> fix: improve type support for
isolated dependencies in pnpm (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20201">#20201</a>)
(Francesco Trotta)</li>
<li>
<a
href="a1f06a350c"><code
class="notranslate">a1f06a3</code></a> fix: correct SourceCode typings
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20114">#20114</a>)
(Pixel998)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="462675af8a"><code
class="notranslate">462675a</code></a> docs: improve web accessibility
by hiding non-semantic character (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20205">#20205</a>)
(루밀LuMir)</li>
<li>
<a
href="c070e65f6b"><code
class="notranslate">c070e65</code></a> docs: correct formatting in <code
class="notranslate">no-irregular-whitespace</code> rule documentation
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20203">#20203</a>)
(루밀LuMir)</li>
<li>
<a
href="b39e71a213"><code
class="notranslate">b39e71a</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="cd39983148"><code
class="notranslate">cd39983</code></a> docs: move <code
class="notranslate">custom-formatters</code> type descriptions to <code
class="notranslate">nodejs-api</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20190">#20190</a>)
(Percy Ma)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="d17c795bf1"><code
class="notranslate">d17c795</code></a> chore: upgrade @eslint/js@9.38.0
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20221">#20221</a>)
(Milos Djermanovic)</li>
<li>
<a
href="25d0e33270"><code
class="notranslate">25d0e33</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="c82b5efa1f"><code
class="notranslate">c82b5ef</code></a> refactor: Use types from
@eslint/core (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20168">#20168</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="ff31609f19"><code
class="notranslate">ff31609</code></a> ci: add Node.js 25 to <code
class="notranslate">ci.yml</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20220">#20220</a>)
(루밀LuMir)</li>
<li>
<a
href="004577eda2"><code
class="notranslate">004577e</code></a> ci: bump github/codeql-action
from 3 to 4 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20211">#20211</a>)
(dependabot[bot])</li>
<li>
<a
href="eac71fb771"><code
class="notranslate">eac71fb</code></a> test: remove use of <code
class="notranslate">nodejsScope</code> option of eslint-scope from tests
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20206">#20206</a>)
(Milos Djermanovic)</li>
<li>
<a
href="4168a18b7e"><code
class="notranslate">4168a18</code></a> chore: fix typo in
legacy-eslint.js (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20202">#20202</a>)
(Sweta Tanwar)</li>
<li>
<a
href="205dbd2d92"><code
class="notranslate">205dbd2</code></a> chore: fix typos (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20200">#20200</a>)
(ntnyq)</li>
<li>
<a
href="dbb200e360"><code
class="notranslate">dbb200e</code></a> chore: use team member's username
when name is not available in data (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20194">#20194</a>)
(Milos Djermanovic)</li>
<li>
<a
href="8962089edb"><code
class="notranslate">8962089</code></a> chore: mark deprecated rules as
available until v11.0.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20184">#20184</a>)
(Pixel998)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="d5d1bdf5fd...e2772811a8">See
the full diff on Github</a>. The new version differs by 59 commits:</p>
<ul>
<li><a
href="e2772811a8"><code>9.39.1</code></a></li>
<li><a
href="4cdf397b30"><code>Build:
changelog update for 9.39.1</code></a></li>
<li><a
href="92db329211"><code>chore:
update `@eslint/js` version to 9.39.1 (#20284)</code></a></li>
<li><a
href="c7ebefc9ea"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="650753ee39"><code>fix:
Only pass node to JS lang visitor methods (#20283)</code></a></li>
<li><a
href="51b51f4f1c"><code>docs:
add a section on when to use extends vs cascading
(#20268)</code></a></li>
<li><a
href="61778f6ca3"><code>chore:
update eslint-config-eslint dependency @eslint/js to ^9.39.0
(#20275)</code></a></li>
<li><a
href="d9ca2fcd9a"><code>ci:
Add rangeStrategy to eslint group in renovate config
(#20266)</code></a></li>
<li><a
href="009e5076ff"><code>test:
fix version tests for ESLint v10 (#20274)</code></a></li>
<li><a
href="b44d42699d"><code>docs:
Update README</code></a></li>
<li><a
href="ac3a60dffc"><code>9.39.0</code></a></li>
<li><a
href="e79017ff52"><code>Build:
changelog update for 9.39.0</code></a></li>
<li><a
href="dfe3c1b203"><code>chore:
update `@eslint/js` version to 9.39.0 (#20270)</code></a></li>
<li><a
href="2375a6de82"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="d3e81e30ee"><code>docs:
Always recommend to include a files property (#20158)</code></a></li>
<li><a
href="15f5c7c168"><code>fix:
forward traversal `step.args` to visitors (#20253)</code></a></li>
<li><a
href="5a1a534e87"><code>fix:
allow JSDoc comments in object-shorthand rule (#20167)</code></a></li>
<li><a
href="cc57d87a3f"><code>feat:
update error loc to key in `no-dupe-class-members`
(#20259)</code></a></li>
<li><a
href="a1f4e52d67"><code>chore:
update `@eslint` dependencies (#20265)</code></a></li>
<li><a
href="e86b813eb6"><code>fix:
Use more types from @eslint/core (#20257)</code></a></li>
<li><a
href="126552fcf3"><code>feat:
update error location in `for-direction` and `no-dupe-args`
(#20258)</code></a></li>
<li><a
href="927272d1f0"><code>fix:
correct `Scope` typings (#20198)</code></a></li>
<li><a
href="c7d3229848"><code>chore:
update dependency @eslint/core to ^0.17.0 (#20256)</code></a></li>
<li><a
href="0f0385f140"><code>docs:
use consistent naming recommendation (#20250)</code></a></li>
<li><a
href="a3b145609a"><code>docs:
Update README</code></a></li>
<li><a
href="37f76d9c53"><code>fix:
use `AST.Program` type for Program node (#20244)</code></a></li>
<li><a
href="cf5f2dd58d"><code>docs:
fix correct tag of `no-useless-constructor` (#20255)</code></a></li>
<li><a
href="27549bc774"><code>chore:
update fuzz testing to not error if code sample minimizer fails
(#20252)</code></a></li>
<li><a
href="10b995c8e5"><code>docs:
add TS options and examples for `nofunc` in `no-use-before-define`
(#20249)</code></a></li>
<li><a
href="167d0970d3"><code>feat:
update `complexity` rule to highlight only static block header
(#20245)</code></a></li>
<li><a
href="2584187e4a"><code>docs:
remove repetitive word in comment (#20242)</code></a></li>
<li><a
href="ae07f0b333"><code>fix:
unify timing report for concurrent linting (#20188)</code></a></li>
<li><a
href="b165d471be"><code>fix:
correct `Rule` typings (#20199)</code></a></li>
<li><a
href="637216bd4f"><code>docs:
update CLI flags migration instructions (#20238)</code></a></li>
<li><a
href="e7cda3bdf1"><code>docs:
Update README</code></a></li>
<li><a
href="a1370ee40e"><code>ci:
bump actions/setup-node from 5 to 6 (#20230)</code></a></li>
<li><a
href="fb97cda70d"><code>fix:
improve error message for missing fix function in suggestions
(#20218)</code></a></li>
<li><a
href="9e7fad4a18"><code>chore:
add script to auto-generate eslint:recommended configuration
(#20208)</code></a></li>
<li><a
href="7b9446f7cc"><code>docs:
handle empty flags sections on the feature flags page
(#20222)</code></a></li>
<li><a
href="8fe511b4c0"><code>9.38.0</code></a></li>
<li><a
href="f961736693"><code>Build:
changelog update for 9.38.0</code></a></li>
<li><a
href="d17c795bf1"><code>chore:
upgrade @eslint/js@9.38.0 (#20221)</code></a></li>
<li><a
href="25d0e33270"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="50c3dfd980"><code>fix:
improve type support for isolated dependencies in pnpm
(#20201)</code></a></li>
<li><a
href="c82b5efa1f"><code>refactor:
Use types from @eslint/core (#20168)</code></a></li>
<li><a
href="ff31609f19"><code>ci:
add Node.js 25 to `ci.yml` (#20220)</code></a></li>
<li><a
href="ce40f74efd"><code>feat:
update `complexity` rule to only highlight function header
(#20048)</code></a></li>
<li><a
href="e37e590aae"><code>feat:
correct `no-loss-of-precision` false positives with `e` notation
(#20187)</code></a></li>
<li><a
href="004577eda2"><code>ci:
bump github/codeql-action from 3 to 4 (#20211)</code></a></li>
<li><a
href="eac71fb771"><code>test:
remove use of `nodejsScope` option of eslint-scope from tests
(#20206)</code></a></li>
<li><a
href="462675af8a"><code>docs:
improve web accessibility by hiding non-semantic character
(#20205)</code></a></li>
<li><a
href="c070e65f6b"><code>docs:
correct formatting in `no-irregular-whitespace` rule documentation
(#20203)</code></a></li>
<li><a
href="b39e71a213"><code>docs:
Update README</code></a></li>
<li><a
href="4168a18b7e"><code>chore:
fix typo in legacy-eslint.js (#20202)</code></a></li>
<li><a
href="205dbd2d92"><code>chore:
fix typos (#20200)</code></a></li>
<li><a
href="a1f06a350c"><code>fix:
correct SourceCode typings (#20114)</code></a></li>
<li><a
href="dbb200e360"><code>chore:
use team member&#39;s username when name is not available in data
(#20194)</code></a></li>
<li><a
href="cd39983148"><code>docs:
move `custom-formatters` type descriptions to `nodejs-api`
(#20190)</code></a></li>
<li><a
href="8962089edb"><code>chore:
mark deprecated rules as available until v11.0.0
(#20184)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-11-09 07:31:49 -05:00
Jordan Pittman
4363426384
Fix validation of source(…) paths (#19274)
Fixes #18833

- [x] Needs tests

Basically we were correctly resolving the path given to `source()`
inside Oxide *but* inside `@tailwindcss/node` when we validated that the
path was a directory we were not.

We incorrectly used the base path of the input file rather than the file
the `source(…)` directive was defined in. This PR fixes that.
2025-11-06 16:24:17 -05:00
Jordan Pittman
e9c9c4f79d
Release v4.1.17 (#19272) 2025-11-06 10:20:36 -05:00
Jordan Pittman
dc6a3ce349
Substitute @variant inside utilities (#19263)
Fixes #19258
2025-11-04 11:28:52 -05:00
Hongxu Xu
babe825f2d
Remove unused crossbeam dependency in oxide (#19256) 2025-11-04 10:37:21 -05:00
depfu[bot]
3e4654d7ea
Update @types/react-dom 19.2.1 → 19.2.2 (patch) (#19259)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react-dom (19.2.1 → 19.2.2) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-11-04 09:49:17 -05:00
depfu[bot]
3cece1d97c Update @napi-rs/cli to version 3.4.1 2025-10-31 14:23:35 +00:00
depfu[bot]
604c22f25e
Update @vitejs/plugin-react 5.0.4 → 5.1.0 (minor) (#19241)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (5.0.4 → 5.1.0) ·
[Repo](https://github.com/vitejs/vite-plugin-react) ·
[Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)



<details>
<summary>Release Notes</summary>

<h4>5.1.0 (from changelog)</h4>
<blockquote><h3 dir="auto">Add <code
class="notranslate">@vitejs/plugin-react/preamble</code> virtual module
for SSR HMR (<a
href="https://bounce.depfu.com/github.com/vitejs/vite-plugin-react/pull/890">#890</a>)</h3>
<p dir="auto">SSR applications can now initialize HMR runtime by
importing <code class="notranslate">@vitejs/plugin-react/preamble</code>
at the top of their client entry instead of manually calling <code
class="notranslate">transformIndexHtml</code>. This simplifies SSR setup
for applications that don't use the <code
class="notranslate">transformIndexHtml</code> API.</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>













---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-31 07:35:24 -04:00
depfu[bot]
e71e70eda9
Update magic-string 0.30.19 → 0.30.21 (minor) (#19238)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ magic-string (0.30.19 → 0.30.21) ·
[Repo](https://github.com/rich-harris/magic-string) ·
[Changelog](https://github.com/Rich-Harris/magic-string/blob/master/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/Rich-Harris/magic-string/releases/tag/v0.30.21">0.30.21</a></h4>

<blockquote><p dir="auto"><em>No significant changes</em></p>
<h5 dir="auto">    <a
href="https://bounce.depfu.com/github.com/Rich-Harris/magic-string/compare/v0.30.20...v0.30.21">View
changes on GitHub</a>
</h5></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let
us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="bdef7d5ab5...410fd4d080">See
the full diff on Github</a>. The new version differs by 7 commits:</p>
<ul>
<li><a
href="410fd4d080"><code>chore:
release v0.30.21</code></a></li>
<li><a
href="5c1800935d"><code>chore:
update repository url</code></a></li>
<li><a
href="97ea74d842"><code>chore:
release v0.30.20</code></a></li>
<li><a
href="3f6b5eb3fb"><code>chore:
update deps</code></a></li>
<li><a
href="60177d51ed"><code>ci:
setup OIDC</code></a></li>
<li><a
href="fbcf5d7e59"><code>chore:
create release.yml</code></a></li>
<li><a
href="cde8c33ef5"><code>chore:
update readme (#305)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-31 07:34:21 -04:00
depfu[bot]
4827c465b8
Update @emnapi/core 1.5.0 → 1.6.0 (minor) (#19220)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​emnapi/core (1.5.0 → 1.6.0) ·
[Repo](https://github.com/toyobayashi/emnapi)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/toyobayashi/emnapi/releases/tag/v1.6.0">1.6.0</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>feat: added SharedArrayBuffer api by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/171">#171</a>
</li>
<li>feat: make napi_delete_reference use node_api_basic_env by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/170">#170</a>
</li>
<li>ci: migrate to npm trusted publishing by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/168">#168</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/compare/v1.5.0...v1.6.0"><tt>v1.5.0...v1.6.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let
us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="61628bd5e2...86d68f88ec">See
the full diff on Github</a>. The new version differs by 4 commits:</p>
<ul>
<li><a
href="86d68f88ec"><code>1.6.0</code></a></li>
<li><a
href="100b8a3580"><code>feat:
make napi_delete_reference use node_api_basic_env (#170)</code></a></li>
<li><a
href="bf622fc37b"><code>[Backport]
feat: added SharedArrayBuffer api (#171)</code></a></li>
<li><a
href="971e3d9e37"><code>ci:
migrate to npm trusted publishing (#168)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-28 12:25:07 +00:00
depfu[bot]
4aa763b2a3
Update emnapi 1.5.0 → 1.6.0 (minor) (#19221)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ emnapi (1.5.0 → 1.6.0) ·
[Repo](https://github.com/toyobayashi/emnapi)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/toyobayashi/emnapi/releases/tag/v1.6.0">1.6.0</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>feat: added SharedArrayBuffer api by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/171">#171</a>
</li>
<li>feat: make napi_delete_reference use node_api_basic_env by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/170">#170</a>
</li>
<li>ci: migrate to npm trusted publishing by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/168">#168</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/compare/v1.5.0...v1.6.0"><tt>v1.5.0...v1.6.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="61628bd5e2...86d68f88ec">See
the full diff on Github</a>. The new version differs by 4 commits:</p>
<ul>
<li><a
href="86d68f88ec"><code>1.6.0</code></a></li>
<li><a
href="100b8a3580"><code>feat:
make napi_delete_reference use node_api_basic_env (#170)</code></a></li>
<li><a
href="bf622fc37b"><code>[Backport]
feat: added SharedArrayBuffer api (#171)</code></a></li>
<li><a
href="971e3d9e37"><code>ci:
migrate to npm trusted publishing (#168)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-28 08:18:37 -04:00
depfu[bot]
7bab613749
Update @emnapi/runtime 1.5.0 → 1.6.0 (minor) (#19219)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​emnapi/runtime (1.5.0 → 1.6.0) ·
[Repo](https://github.com/toyobayashi/emnapi)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/toyobayashi/emnapi/releases/tag/v1.6.0">1.6.0</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>feat: added SharedArrayBuffer api by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/171">#171</a>
</li>
<li>feat: make napi_delete_reference use node_api_basic_env by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/170">#170</a>
</li>
<li>ci: migrate to npm trusted publishing by <a
href="https://bounce.depfu.com/github.com/toyobayashi">@toyobayashi</a>
in <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/168">#168</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/compare/v1.5.0...v1.6.0"><tt>v1.5.0...v1.6.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="61628bd5e2...86d68f88ec">See
the full diff on Github</a>. The new version differs by 4 commits:</p>
<ul>
<li><a
href="86d68f88ec"><code>1.6.0</code></a></li>
<li><a
href="100b8a3580"><code>feat:
make napi_delete_reference use node_api_basic_env (#170)</code></a></li>
<li><a
href="bf622fc37b"><code>[Backport]
feat: added SharedArrayBuffer api (#171)</code></a></li>
<li><a
href="971e3d9e37"><code>ci:
migrate to npm trusted publishing (#168)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-28 08:18:27 -04:00
depfu[bot]
91694fbfc1 Update @playwright/test to version 1.56.1 2025-10-24 11:53:15 +00:00
depfu[bot]
effcd27e17
Update all of nextjs 15.5.4 → 15.5.6 (patch) (#19194)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint-config-next (15.5.4 → 15.5.6)





Sorry, we couldn't find anything useful about this release.



#### ✳️ next (15.5.4 → 15.5.6) ·
[Repo](https://github.com/vercel/next.js)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.5.6">15.5.6</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon
octicon-info mr-2" viewbox="0 0 16 16" version="1.1" width="16"
height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0
8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1
7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1
0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0
2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>Turbopack: don't define process.cwd() in node_modules <a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/83452">#83452</a>
</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to <a
href="https://bounce.depfu.com/github.com/mischnic">@mischnic</a> for
helping!</p></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.5.5">15.5.5</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon
octicon-info mr-2" viewbox="0 0 16 16" version="1.1" width="16"
height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0
8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1
7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1
0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0
2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>Split code-frame into separate compiled package (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/84238">#84238</a>)</li>
<li>Add deprecation warning to Runtime config (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/84650">#84650</a>)</li>
<li>fix: unstable_cache should perform blocking revalidation during ISR
revalidation (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/84716">#84716</a>)</li>
<li>feat: <code
class="notranslate">experimental.middlewareClientMaxBodySize</code> body
cloning limit (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/84722">#84722</a>)</li>
<li>fix: missing next/link types with typedRoutes (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/84779">#84779</a>)</li>
</ul>
<h3 dir="auto">Misc Changes</h3>
<ul dir="auto">
<li>docs: early October improvements and fixes (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/84334">#84334</a>)</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to <a
href="https://bounce.depfu.com/github.com/devjiwonchoi">@devjiwonchoi</a>,
<a href="https://bounce.depfu.com/github.com/ztanner">@ztanner</a>, and
<a href="https://bounce.depfu.com/github.com/icyJoseph">@icyJoseph</a>
for helping!</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="40f1d7814d...55ef0e3ebc">See
the full diff on Github</a>. The new version differs by 10 commits:</p>
<ul>
<li><a
href="55ef0e3ebc"><code>v15.5.6</code></a></li>
<li><a
href="92bbbb1bec"><code>Backport:
don&#39;t define `process.cwd()` in node_modules
(#84957)</code></a></li>
<li><a
href="f895b72762"><code>Fix
url-imports test on 15-5 (#84966)</code></a></li>
<li><a
href="81f530db26"><code>v15.5.5</code></a></li>
<li><a
href="9abbc0e9eb"><code>[backport]
fix: missing `next/link` types with `typedRoutes` (#82814)
(#84779)</code></a></li>
<li><a
href="121e1b566f"><code>[backport]
docs: early October improvements and fixes (#84334)</code></a></li>
<li><a
href="1b276c98f9"><code>[backport]:
`experimental.middlewareClientMaxBodySize` (#84722)</code></a></li>
<li><a
href="2061f04132"><code>[backport]
fix: unstable_cache should perform blocking revalidation during ISR
revalidation (#84716)</code></a></li>
<li><a
href="ce3d9639d1"><code>[backport]
Add deprecation warning to Runtime config (#84168)
(#84650)</code></a></li>
<li><a
href="ec69752d9e"><code>[backport]
Split code-frame into separate compiled package (#84174)
(#84238)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-24 07:37:00 -04:00
Robin Malfait
2530934a4f
update changelog 2025-10-23 12:55:39 +02:00
Robin Malfait
cbbbe84475
Release 4.1.16 (#19185) 2025-10-23 12:32:27 +02:00
Robin Malfait
601d6719f8
Fix incorrect colors used in pseudo-element (#19184)
This PR essentially reverts
https://github.com/tailwindlabs/tailwindcss/pull/19069

We added the nested `&` inside the `@supports` query when we create
fallbacks for color-mix so that devtools (Safari) doesn't freak out.
This works in most cases, however, if you have a parent pseudo element
like `::before`, then the browser will not allow the nested `&`
resulting in invalid CSS.

This PR means that we go back to the broken devtools experience in
Safari, but at least the CSS is valid and works as expected.

Fixes: #19183
2025-10-23 10:07:01 +00:00
Jordan Pittman
a41add9fab
Improve canonicalization for & > :pseudo and & :pseudo arbitrary variants (#19178)
This improves canonicalization of arbitrary variants that use pseudo
classes a bit.

Before this we would see `[&_:first-child]:flex` and leave it be when it
can instead be written as `**:first:flex`. Likewise, for pseudo classes
that don't have a variant, we can still simplify things a bit as
`[&_:--custom]` can be written `**:[:--custom]`.
2025-10-22 08:29:08 -04:00
Jordan Pittman
0113b88fbd
Fix canonicalization of arbitrary variants with attribute selectors (#19176)
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1481

We were detecting when we needed to apply the `*` and `**` variants and
even detecting attribute selectors. However we only cleaned up the
attribute selectors when they were `data-*` or `aria-*` attributes. This
resulted in a "loop" of sorts because we'd:
- See something like `[&_>_[foo]]:flex`
- Notice that it needs a `*` variant
- Add the `*` variant, giving `*:[&_>[foo]]:flex`
- But fail to cleanup the remainder of the variant

This then meant that we'd see the `*:[&_>[foo]]:flex` the next time we
checked, notice that it still needed a `*` variant, and repeat…

This PR fixes this case to clean up the selector.
2025-10-22 08:19:54 -04:00
Jordan Pittman
29687e0183
Discard candidates with an empty data type (#19172)
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1479

Maybe should close
https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1480 —
perhaps we can find a workaround there for older versions?

We're building up a class name in code to validate if something is a
valid variant: `{variant}:[color:red]`

if `{variant}` got replaced with `bg-[` then we'd produce
`bg-[:[color:red]` and this parsed as a valid candidate:
```
bg-[:[color:red]
^^                root: `bg`
   ^              data type: `` (empty string) — this should be invalid
     ^^^^^^^^^^   value: `[color:red`
```

The value isn't valid _but_ the syntax for arbitrary values is pretty
lax in core. Oxide already won't pick something like this up though so
no problem there. Only a problem for something like IntelliSense or
clients using the compile() API directly.
2025-10-22 06:14:43 -04:00
Robin Malfait
56e7f3b2c2
Improve memory usage during canonicalization (#19171)
This PR drastically improves the memory usage when performing
canonicalization if you swap out the underlying DesignSystem often. This
will be most noticeable in Intellisense.

The big issue we had is that we used module scoped Map objects where we
cache data based on the DesignSystem. If you then create new design
systems (often), then the cache would just keep growing and growing.

This PR solves that by essentially storing all the caches on the Design
System itself. This way, when you throw away a Design System, all the
caches go with it.

Another approach would've been to use a WeakMap, but then we would have
to make sure that no strong references to the DesignSystem exist
anywhere else, otherwise we would still have the same memory issues.

Note: make sure to go commit by commit and use `?w=1` to ignore
whitespace changes.

## Test plan

1. All existing tests pass

Not super sure how to test this as part of the test suite without making
it slow But I also don't think that's super necessary either. Here is an
experiment I did where I introduce 5 design systems:
<img width="1326" height="274" alt="image"
src="https://github.com/user-attachments/assets/817025e3-0f5b-44be-949b-54ed08f5b3fb"
/>

On the current `main` branch, this looks like:
<img width="619" height="69" alt="image"
src="https://github.com/user-attachments/assets/588ae99b-c978-4c01-bfd1-5cc0725723a8"
/>


In this PR, the memory usage looks like:
<img width="512" height="56" alt="image"
src="https://github.com/user-attachments/assets/0052ad21-7b99-4edf-8a14-8ccef52362db"
/>

The memory usage is stable, but to actually prove that we can still
track multiple design systems, let's track them all in a `Set` so
garbage collection cannot get rid of the unused design system objects:
<img width="847" height="230" alt="image"
src="https://github.com/user-attachments/assets/5f044927-3d53-4c15-8145-78eb2b4d6d54"
/>

Now we're sort of back to the current situation on `main`:
<img width="507" height="53" alt="image"
src="https://github.com/user-attachments/assets/868c0238-8646-41ce-8151-e0ef6dd17d64"
/>
2025-10-21 16:55:30 +02:00
Jordan Pittman
3a4ab8201b
Stop suggesting legacy utilities (#19169)
It was already supposed to work this way — and it appeared via the tests
that it did — but the tests weren't loading the design system normally
so when we split the legacy utilities into a separate file it stopped
testing this properly.

The setup here is a bit iffy but the gist is:
- We allow static utilities to be suggested by default
- A static utility can *opt out* of suggestions by explicitly
registering themselves to return none.
2025-10-21 06:54:10 -04:00
Robin Malfait
7537e34fd1
Ignore --tw- variables during internal signature computation (#19156)
This PR improves some of the signature computation logic. Right now,
when you want to convert `[font-weight:400]` to `font-normal` it's not
going to work because the signatures don't like up:

```css
/* [font-weight:400] */
.x {
  font-weight: 400;
}

/* font-normal */
.x {
  --tw-font-weight: 400;
  font-weight: 400;
}
```

So this PR essentially ignores `--tw-{property}` _if_ the `{property}`
exists with the exact same value.

The main reason we have this, is to make composition of utilities
easier.

As with any of these upgrades, they are the expected behavior in most
cases, but there could always be a case where you don't want this, but
that's why the upgrade tool requires you to review each change before
applying it. Intellisense will recommend the simplified class, but it's
up to you to decide if you want to apply it or not.

There is a known edge case for `leading-{num}`, because the property is
`line-height` and the CSS variable is `--tw-leading`. Right now we map
`--tw-leading` to `line-height` but we can also update the leading
classes to use `--tw-line-height` instead but that feels like a bigger
breaking change _if_ people rely on these internal CSS variables...
2025-10-20 15:21:07 +00:00
Robin Malfait
66c18ca8a4
Collapse multiple utilities (#19147)
Some commits look like a lot of changes, but they just move things
around, so best to use `?w=1` when viewing commit by commit.

This PR adds a new feature to the `designSystem.canonicalizeCandidates`
to collapse multiple utilities to fewer utilities. To make this
possible, we also have to convert some logical properties to physical
properties (controllable via an option).

```ts
ds.canonicalizeCandidates(['w-4', 'h-4'], {
  collapse: true // Default `true`
  logicalToPhysical: true // Default `true`
}) // → ['size-4']
```

We can already compute the signature of each utility, where if two
utilities have the same signature they are considered the same.

However it's kind of impossible to generate all combinations of all
utilities ever to figure out if there is a potential collapse possible.
Even if we just focus on the incoming list of candidates, there could
still be a lot of classes. So instead we have to be a bit more clever.

First, we group candidates together by the used variants and if the
important `!` flag was used. We can improve this in the future, but for
now we won't even try to combine `hover:w-4 h-4`.

Next, for each candidate, we figure out which property and value it
uses. We can build up a lookup table for this. We already did this
process for all utilities in the system as well.

The lookup table for `w-4 h-4 p-4` might look something like this.

```json
{
  "width": {
    "16px": ["w-4", "size-4"],
  },
  "height": {
    "16px": ["h-4", "size-4"],
  },
  "padding": {
    "16px": ["p-4"],
  }
}
```

Next, we can build groups of candidates where an intersection exists in
the lookup table. In the example above, we can see that `w-4` and `h-4`
both map to `size-4` for the value `16px`. So we can group these two
candidates. The `p-4`d doesn't intersect with anything else, so it
remains alone. This also means that we only have to generate
combinations for two candidates (2^2 = 4) instead of three (2^3 = 8). In
practice, your class list might have many classes, so keeping this
number low is important.

When we generate combinations, we will generate the most amount of
candidates first so we have the largest collapse possible. We also stop
when we reach <= 1 combinations because we need at least two candidates
to collapse.

Since this uses the internal design system, if you have custom
`@utility`s, this will work as expected.

```css
@utility example {
  @apply border rounded p-4;
}
```

If you then use:
```html
<div class="border m-0 rounded p-4"></div>
```

Then we can collapse this to:
```html
<div class="example m-0"></div>
```

But even if we used:
```html
<div class="border m-0 rounded pt-4 pb-4 px-4"></div>
```

It would still collapse to:
```html
<div class="example m-0"></div>
```

...because the `pt-4` and `pb-4` can collapse to `py-4`, and `py-4` and
`px-4` can collapse to `p-4`.

This is also where that logical to physical conversion comes into play,
because while `pt-4` and `pb-4` set the physical `padding-top` and
`padding-bottom` properties. The `py-4` utility sets the logical
`padding-block` property. So we internally transform `padding-block` to
the `padding-top` and `padding-bottom` physical properties. The funny
thing is that this logical to physical conversion actually means that we
will convert `pt-4` and `pb-4` from _physical_ to _logical_ properties,
because we converted to `py-4`...

In _most_ cases it's fine, and even preferred to use `py-1` over `pt-1
pb-1`, but in case it's not, then you can disable the
`logicalToPhysical` option.

## Test plan

Added some dedicated tests for this new functionality.
2025-10-20 15:15:31 +00:00
Jordan Pittman
6c4081421e
Incorporate changes from latest ignore crate (#19148)
This updates our internal fork of the `ignore` crate to incorporate
changes from the latest release
[v0.4.24](https://github.com/BurntSushi/ripgrep/compare/ignore-0.4.23...ignore-0.4.24).

Aside: We should look into opening issues about the changes we had to
make to see if they could be addressed in the crate itself so we can get
rid of our fork.
2025-10-20 09:49:40 -04:00
depfu[bot]
a6a7e4cbaf
Update jiti 2.6.0 → 2.6.1 (patch) (#19160)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ jiti (2.6.0 → 2.6.1) · [Repo](https://github.com/unjs/jiti) ·
[Changelog](https://github.com/unjs/jiti/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/unjs/jiti/releases/tag/v2.6.1">2.6.1</a></h4>

<blockquote><p dir="auto"><a
href="https://bounce.depfu.com/github.com/unjs/jiti/compare/v2.6.0...v2.6.1">compare
changes</a></p>
<h3 dir="auto">🩹 Fixes</h3>
<ul dir="auto">
<li>
<strong>interop:</strong> Only passthrough default if it is not a
promise (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/408">#408</a>)</li>
</ul>
<h3 dir="auto">📦 Build</h3>
<ul dir="auto">
<li>Revert to <code class="notranslate">terser-webpack-plugin</code> (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/407">#407</a>)</li>
</ul>
<h3 dir="auto">❤️ Contributors</h3>
<ul dir="auto">
<li>Kricsleo (<a
href="https://bounce.depfu.com/github.com/kricsleo">@kricsleo</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="82919cff8b...aedcdee7fe">See
the full diff on Github</a>. The new version differs by 6 commits:</p>
<ul>
<li><a
href="aedcdee7fe"><code>chore(release):
v2.6.1</code></a></li>
<li><a
href="8b41497481"><code>chore:
remove unused code</code></a></li>
<li><a
href="974ca4008a"><code>chore:
update deps</code></a></li>
<li><a
href="e840692427"><code>build:
revert to `terser-webpack-plugin` (#407)</code></a></li>
<li><a
href="092cdd9952"><code>fix(interop):
only passthrough default if it is not a promise (#408)</code></a></li>
<li><a
href="037c646c80"><code>chore:
update bench</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-20 14:59:11 +02:00
Robin Malfait
b2e2435ccb
Release 4.1.15 (#19159) 2025-10-20 14:48:42 +02:00
Robin Malfait
3b636b74d7
Mark break-words as deprecated, and upgrade to wrap-break-word (#19157)
This PR marks `break-words` as deprecated (such that intellisense
doesn't suggest it anymore). Updates the upgrade tooling to prefer
`wrap-break-word` instead.

Note: `break-words` will still work as expected.

## Test plan

1. `break-words` still generates the correct CSS.
2. Intellisense doesn't suggest `break-words` anymore.
3. Upgrade tooling suggests `wrap-break-word` instead of `break-words`.
2025-10-20 14:14:25 +02:00
depfu[bot]
fb0f432e48
Update @types/bun 1.2.22 → 1.3.0 (minor) (#19145) 2025-10-20 12:20:33 +02:00
Philipp Spiess
afe3de3bd6
Revert agentic issue-triage workflow (#19155)
Reverts #19057

Unfortunately wasn't able to get this to work since the permissions will
always cause it to not be triggered when someone outside our GitHub orgs
contributes.
2025-10-20 12:20:08 +02:00
depfu[bot]
fee19b2d4e
Update bun 1.2.22 → 1.3.0 (minor) (#19144) 2025-10-20 11:43:39 +02:00
Philipp Spiess
70f27d50a6
Remove Oxide postinstall script (#19149)
This PR effectively reverts #17929.

The bug in npm that required it was fixed a couple of months ago and
with recent changes to pnpm that requires manually approving all
postinstall scripts, this is creating some unnecessary noise.
2025-10-19 10:50:21 +02:00
Jordan Pittman
48c274d83e
Update Lightning CSS to 1.30.2 (#19143) 2025-10-17 15:41:49 -04:00
depfu[bot]
e25c1f0594
Update eslint 9.36.0 → 9.37.0 (minor) (#19142)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.36.0 → 9.37.0) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.37.0">9.37.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="39f7fb493a"><code
class="notranslate">39f7fb4</code></a> feat: <code
class="notranslate">preserve-caught-error</code> should recognize all
static "cause" keys (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20163">#20163</a>)
(Pixel998)</li>
<li>
<a
href="f81eabc584"><code
class="notranslate">f81eabc</code></a> feat: support TS syntax in <code
class="notranslate">no-restricted-imports</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19562">#19562</a>)
(Nitin Kumar)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="a129cced7a"><code
class="notranslate">a129cce</code></a> fix: correct <code
class="notranslate">no-loss-of-precision</code> false positives for
leading zeros (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20164">#20164</a>)
(Francesco Trotta)</li>
<li>
<a
href="09e04fcc3f"><code
class="notranslate">09e04fc</code></a> fix: add missing AST token types
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20172">#20172</a>)
(Pixel998)</li>
<li>
<a
href="861c6da2bd"><code
class="notranslate">861c6da</code></a> fix: correct <code
class="notranslate">ESLint</code> typings (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20122">#20122</a>)
(Pixel998)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="b950359c5f"><code
class="notranslate">b950359</code></a> docs: fix typos across the docs
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20182">#20182</a>)
(루밀LuMir)</li>
<li>
<a
href="42498a2798"><code
class="notranslate">42498a2</code></a> docs: improve ToC accessibility
by hiding non-semantic character (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20181">#20181</a>)
(Percy Ma)</li>
<li>
<a
href="29ea092b93"><code
class="notranslate">29ea092</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="5c97a04578"><code
class="notranslate">5c97a04</code></a> docs: show <code
class="notranslate">availableUntil</code> in deprecated rule banner (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20170">#20170</a>)
(Pixel998)</li>
<li>
<a
href="90a71bf502"><code
class="notranslate">90a71bf</code></a> docs: update <code
class="notranslate">README</code> files to add badge and instructions
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20115">#20115</a>)
(루밀LuMir)</li>
<li>
<a
href="1603ae1526"><code
class="notranslate">1603ae1</code></a> docs: update references from
<code class="notranslate">master</code> to <code
class="notranslate">main</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20153">#20153</a>)
(루밀LuMir)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="afe8a13469"><code
class="notranslate">afe8a13</code></a> chore: update <code
class="notranslate">@eslint/js</code> dependency to version 9.37.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20183">#20183</a>)
(Francesco Trotta)</li>
<li>
<a
href="abee4ca1fa"><code
class="notranslate">abee4ca</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="fc9381f6ca"><code
class="notranslate">fc9381f</code></a> chore: fix typos in comments (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20175">#20175</a>)
(overlookmotel)</li>
<li>
<a
href="e1574a22d3"><code
class="notranslate">e1574a2</code></a> chore: unpin jiti (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20173">#20173</a>)
(renovate[bot])</li>
<li>
<a
href="e1ac05e2fa"><code
class="notranslate">e1ac05e</code></a> refactor: mark <code
class="notranslate">ESLint.findConfigFile()</code> as <code
class="notranslate">async</code>, add missing docs (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20157">#20157</a>)
(Pixel998)</li>
<li>
<a
href="347906d627"><code
class="notranslate">347906d</code></a> chore: update eslint (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20149">#20149</a>)
(renovate[bot])</li>
<li>
<a
href="0cb5897e24"><code
class="notranslate">0cb5897</code></a> test: remove tmp dir created for
circular fixes in multithread mode test (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20146">#20146</a>)
(Milos Djermanovic)</li>
<li>
<a
href="bb995665e3"><code
class="notranslate">bb99566</code></a> ci: pin <code
class="notranslate">jiti</code> to version 2.5.1 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20151">#20151</a>)
(Pixel998)</li>
<li>
<a
href="177f669adc"><code
class="notranslate">177f669</code></a> perf: improve worker count
calculation for <code class="notranslate">"auto"</code> concurrency (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20067">#20067</a>)
(Francesco Trotta)</li>
<li>
<a
href="448b57bca3"><code
class="notranslate">448b57b</code></a> chore: Mark deprecated formatting
rules as available until v11.0.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20144">#20144</a>)
(Milos Djermanovic)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="b4857e54e5...d5d1bdf5fd">See
the full diff on Github</a>. The new version differs by 23 commits:</p>
<ul>
<li><a
href="d5d1bdf5fd"><code>9.37.0</code></a></li>
<li><a
href="94865ff41c"><code>Build:
changelog update for 9.37.0</code></a></li>
<li><a
href="afe8a13469"><code>chore:
update `@eslint/js` dependency to version 9.37.0
(#20183)</code></a></li>
<li><a
href="abee4ca1fa"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="b950359c5f"><code>docs:
fix typos across the docs (#20182)</code></a></li>
<li><a
href="42498a2798"><code>docs:
improve ToC accessibility by hiding non-semantic character
(#20181)</code></a></li>
<li><a
href="fc9381f6ca"><code>chore:
fix typos in comments (#20175)</code></a></li>
<li><a
href="e1574a22d3"><code>chore:
unpin jiti (#20173)</code></a></li>
<li><a
href="29ea092b93"><code>docs:
Update README</code></a></li>
<li><a
href="a129cced7a"><code>fix:
correct `no-loss-of-precision` false positives for leading zeros
(#20164)</code></a></li>
<li><a
href="09e04fcc3f"><code>fix:
add missing AST token types (#20172)</code></a></li>
<li><a
href="5c97a04578"><code>docs:
show `availableUntil` in deprecated rule banner (#20170)</code></a></li>
<li><a
href="39f7fb493a"><code>feat:
`preserve-caught-error` should recognize all static &quot;cause&quot;
keys (#20163)</code></a></li>
<li><a
href="f81eabc584"><code>feat:
support TS syntax in `no-restricted-imports` (#19562)</code></a></li>
<li><a
href="e1ac05e2fa"><code>refactor:
mark `ESLint.findConfigFile()` as `async`, add missing docs
(#20157)</code></a></li>
<li><a
href="90a71bf502"><code>docs:
update `README` files to add badge and instructions
(#20115)</code></a></li>
<li><a
href="861c6da2bd"><code>fix:
correct `ESLint` typings (#20122)</code></a></li>
<li><a
href="347906d627"><code>chore:
update eslint (#20149)</code></a></li>
<li><a
href="1603ae1526"><code>docs:
update references from `master` to `main` (#20153)</code></a></li>
<li><a
href="0cb5897e24"><code>test:
remove tmp dir created for circular fixes in multithread mode test
(#20146)</code></a></li>
<li><a
href="bb995665e3"><code>ci:
pin `jiti` to version 2.5.1 (#20151)</code></a></li>
<li><a
href="177f669adc"><code>perf:
improve worker count calculation for `&quot;auto&quot;` concurrency
(#20067)</code></a></li>
<li><a
href="448b57bca3"><code>chore:
Mark deprecated formatting rules as available until v11.0.0
(#20144)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-17 14:41:16 -04:00
depfu[bot]
71e1931044
Update @napi-rs/wasm-runtime 1.0.5 → 1.0.7 (patch) (#19140)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​napi-rs/wasm-runtime (1.0.5 → 1.0.7) ·
[Repo](https://github.com/napi-rs/napi-rs)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-17 11:00:55 -04:00
Benoît Rouleau
89cbfc7b2d
Add optimize option to @tailwindcss/vite plugin (#19131)
Adds an `optimize` option to the Vite plugin that matches the API and
behavior of the PostCSS plugin.

Supports three formats:
- `optimize: false` - disable optimization
- `optimize: true` - enable optimization with minification
- `optimize: { minify: false }` - enable optimization without
minification

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-10-17 15:00:25 +00:00
Robin Malfait
acb27ef9e9
Generalize the walk implementation (#19126)
This PR generalizes the `walk` implementations we have. What's important
here is that we currently have multiple `walk` implementations, one for
the AST, one for the `SelectorParser`, one for the `ValueParser`.

Sometimes, we also need to go up the tree in a depth-first manner. For
that, we have `walkDepth` implementations.

The funny thing is, all these implementations are very very similar,
even the kinds of trees are very similar. They are just objects with
`nodes: []` as children.

So this PR introduces a generic `walk` function that can work on all of
these trees.

There are also some situations where you need to go down and back up the
tree. For this reason, we added an `enter` and `exit` phase:

```ts
walk(ast, {
  enter(node, ctx) {},
  exit(node, ctx) {},
})
```

This means that you don't need to `walk(ast)` and later `walkDepth(ast)`
in case you wanted to do something _after_ visiting all nodes.

The API of these walk functions also slightly changed to fix some
problems we've had before. One is the `replaceWith` function. You could
technically call it multiple times, but that doesn't make sense so
instead you always have to return an explicit `WalkAction`. The
possibilities are:

```ts
// The ones we already had
WalkAction.Continue // Continue walking as normal, the default behavior
WalkAction.Skip // Skip walking the `nodes` of the current node
WalkAction.Stop // Stop the entire walk

// The new ones
WalkAction.Replace(newNode) // Replace the current node, and continue walking the new node(s)
WalkAction.ReplaceSkip(newNode) // Replace the current node, but don't walk the new node(s)
WalkAction.ReplaceStop(newNode) // Replace the current node, but stop the entire walk
```

To make sure that we can walk in both directions, and to make sure we
have proper control over when to walk which nodes, the `walk` function
is implemented in an iterative manner using a stack instead of
recursion.

This also means that a `WalkAction.Stop` or `WalkAction.ReplaceStop`
will immediately stop the walk, without unwinding the entire call stack.

Some notes: 
- The CSS AST does have `context` nodes, for this we can build up the
context lazily when we need it. I added a `cssContext(ctx)` that gives
you an enhanced context including the `context` object that you can read
information from.
- The second argument of the `walk` function can still be a normal
function, which is equivalent to `{ enter: fn }`.

Let's also take a look at some numbers. With this new implementation,
each `walk` is roughly ~1.3-1.5x faster than before. If you look at the
memory usage (especially in Bun) we go from `~2.2GB` peak memory usage,
to `~300mb` peak memory usage.

Some benchmarks on small and big trees (M1 Max):

<img width="2062" height="1438" alt="image"
src="https://github.com/user-attachments/assets/5ec8c22a-9de8-4e08-869a-18c0d30eb7e8"
/>

<img width="2062" height="1246" alt="image"
src="https://github.com/user-attachments/assets/e89d4b8e-29ca-4aee-8fd2-b7c043d3bbf4"
/>

We also ran some benchmarks on @thecrypticace's M3 Max:

<img width="1598" height="1452" alt="image"
src="https://github.com/user-attachments/assets/3b06b6fe-2497-4f24-a428-1a0e2af3896a"
/>

In node the memory difference isn't that big, but the performance itself
is still better:

<img width="2034" height="1586" alt="image"
src="https://github.com/user-attachments/assets/ef28ae14-b53e-4912-9621-531f3b02898f"
/>


In summary:

1. Single `walk` implementation for multiple use cases
2. Support for `enter` and `exit` phases
3. New `WalkAction` possibilities for better control
4. Overall better performance
5. ... and lower memory usage

## Test plan

1. All tests still pass (but had to adjust some of the APIs if `walk`
was used inside tests).
2. Added new tests for the `walk` implementation
3. Ran local benchmarks to verify the performance improvements
2025-10-15 21:28:12 +02:00
depfu[bot]
fc63ce7fbc
Update @vitejs/plugin-react 5.0.3 → 5.0.4 (patch) (#19129)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (5.0.3 → 5.0.4) ·
[Repo](https://github.com/vitejs/vite-plugin-react) ·
[Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)



<details>
<summary>Release Notes</summary>

<h4>5.0.4 (from changelog)</h4>
<blockquote><h3 dir="auto">Perf: use native refresh wrapper plugin in
rolldown-vite (<a
href="https://bounce.depfu.com/github.com/vitejs/vite-plugin-react/pull/881">#881</a>)</h3></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>













---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-15 07:58:31 -04:00
Robin Malfait
de6b54c307
Fix parsing issue when \t is used in at-rules (#19130)
This PR fixes an issue where an at-rule that used `@name\tparams` didn't
properly parse because we didn't properly handle `\t`.

## Test plan

1. Added failing tests
2. Made them pass

Fixes: #19127
2025-10-15 11:50:02 +02:00
Robin Malfait
22858ac4ae
Make TypeScript a bit more happy (#19124)
While working on another PR, I noticed that some files had missing
properties and made TypeScript unhappy. Let's make TypeScript happy
again...
2025-10-14 19:52:46 +00:00
depfu[bot]
c7f6303070 Update semver to version 7.7.3 2025-10-14 18:22:58 +00:00
depfu[bot]
28c72314fc
Update @types/react 19.1.13 → 19.2.2 (minor) (#19116)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react (19.1.13 → 19.2.2) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-14 06:41:33 -04:00
Jordan Pittman
f945c3d29f
Don’t index into strings with the theme(…) function (#19111)
Indexing into a string is only ever going to produce a single character
and is almost guaranteed to be a mistake. The legacy keypath notation is
meant to traverse objects and arrays — not strings.

Fixes #19104
2025-10-13 11:44:33 -04:00
Jordan Pittman
2cba8319ce
Clone AST nodes used in staticValues (#19110)
These were getting mutated but they were shared instead of being
re-created for new candidates. Cloning the nodes fixes this so mutation
of the AST nodes doesn’t stick around.

Fixes #19108
2025-10-13 10:23:09 -04:00
depfu[bot]
249bed0916
Update @napi-rs/cli 3.2.0 → 3.3.0 (minor) (#19102)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​napi-rs/cli (3.2.0 → 3.3.0) ·
[Repo](https://github.com/napi-rs/napi-rs)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-10-10 12:44:16 -04:00
Robin Malfait
c67c0c54cf
Canonicalize dimensions (#19101) 2025-10-10 17:44:53 +02:00
depfu[bot]
496a1e9362
Update @types/react-dom 19.1.9 → 19.2.1 (minor) (#19088)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react-dom (19.1.9 → 19.2.1) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-10-09 14:58:37 -04:00
depfu[bot]
53a8d3bc90
Update all of react 19.1.1 → 19.2.0 (minor) (#19087)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ react (19.1.1 → 19.2.0) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.2.0">19.2.0</a></h4>

<blockquote><p dir="auto">Below is a list of all new features, APIs, and
bug fixes.</p>
<p dir="auto">Read the <a
href="https://react.dev/blog/2025/10/01/react-19-2">React 19.2 release
post</a> for more information.</p>
<h2 dir="auto">New React Features</h2>
<ul dir="auto">
<li>
<a href="https://react.dev/reference/react/Activity"><code
class="notranslate">&lt;Activity&gt;</code></a>: A new API to hide and
restore the UI and internal state of its children.</li>
<li>
<a href="https://react.dev/reference/react/useEffectEvent"><code
class="notranslate">useEffectEvent</code></a> is a React Hook that lets
you extract non-reactive logic into an <a
href="https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event">Effect
Event</a>.</li>
<li>
<a href="https://react.dev/reference/react/cacheSignal"><code
class="notranslate">cacheSignal</code></a> (for RSCs) lets your know
when the <code class="notranslate">cache()</code> lifetime is over.</li>
<li>
<a
href="https://react.dev/reference/developer-tooling/react-performance-tracks">React
Performance tracks</a> appear on the Performance panel’s timeline in
your browser developer tools</li>
</ul>
<h2 dir="auto">New React DOM Features</h2>
<ul dir="auto">
<li>Added resume APIs for partial pre-rendering with Web Streams:
<ul dir="auto">
<li>
<a href="https://react.dev/reference/react-dom/server/resume"><code
class="notranslate">resume</code></a>: to resume a prerender to a
stream.</li>
<li>
<a
href="https://react.dev/reference/react-dom/static/resumeAndPrerender"><code
class="notranslate">resumeAndPrerender</code></a>: to resume a prerender
to HTML.</li>
</ul>
</li>
<li>Added resume APIs for partial pre-rendering with Node Streams:
<ul dir="auto">
<li>
<a
href="https://react.dev/reference/react-dom/server/resumeToPipeableStream"><code
class="notranslate">resumeToPipeableStream</code></a>: to resume a
prerender to a stream.</li>
<li>
<a
href="https://react.dev/reference/react-dom/static/resumeAndPrerenderToNodeStream"><code
class="notranslate">resumeAndPrerenderToNodeStream</code></a>: to resume
a prerender to HTML.</li>
</ul>
</li>
<li>Updated <a
href="https://react.dev/reference/react-dom/static/prerender"><code
class="notranslate">prerender</code></a> APIs to return a <code
class="notranslate">postponed</code> state that can be passed to the
<code class="notranslate">resume</code> APIs.</li>
</ul>
<h2 dir="auto">Notable changes</h2>
<ul dir="auto">
<li>React DOM now batches suspense boundary reveals, matching the
behavior of client side rendering. This change is especially noticeable
when animating the reveal of Suspense boundaries e.g. with the upcoming
<code class="notranslate">&lt;ViewTransition&gt;</code> Component. React
will batch as much reveals as possible before the first paint while
trying to hit popular first-contentful paint metrics.</li>
<li>Add Node Web Streams (<code class="notranslate">prerender</code>,
<code class="notranslate">renderToReadableStream</code>) to
server-side-rendering APIs for Node.js</li>
<li>Use underscore instead of <code class="notranslate">:</code> IDs
generated by useId</li>
</ul>
<h2 dir="auto">All Changes</h2>
<h3 dir="auto">React</h3>
<ul dir="auto">
<li>
<code class="notranslate">&lt;Activity /&gt;</code> was developed over
many years, starting before <code
class="notranslate">ClassComponent.setState</code> (<a
href="https://bounce.depfu.com/github.com/acdlite">@acdlite</a> <a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
and many others)</li>
<li>Stringify context as "SomeContext" instead of "SomeContext.Provider"
(<a href="https://bounce.depfu.com/github.com/kassens">@kassens</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33507">#33507</a>)</li>
<li>Include stack of cause of React instrumentation errors with <code
class="notranslate">%o</code> placeholder (<a
href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34198">#34198</a>)</li>
<li>Fix infinite <code class="notranslate">useDeferredValue</code> loop
in popstate event (<a
href="https://bounce.depfu.com/github.com/acdlite">@acdlite</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32821">#32821</a>)</li>
<li>Fix a bug when an initial value was passed to <code
class="notranslate">useDeferredValue</code> (<a
href="https://bounce.depfu.com/github.com/acdlite">@acdlite</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34376">#34376</a>)</li>
<li>Fix a crash when submitting forms with Client Actions (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33055">#33055</a>)</li>
<li>Hide/unhide the content of dehydrated suspense boundaries if they
resuspend (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32900">#32900</a>)</li>
<li>Avoid stack overflow on wide trees during Hot Reload (<a
href="https://bounce.depfu.com/github.com/sophiebits">@sophiebits</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34145">#34145</a>)</li>
<li>Improve Owner and Component stacks in various places (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>,
<a href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a>: <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33629">#33629</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33724">#33724</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32735">#32735</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33723">#33723</a>)</li>
<li>Add <code class="notranslate">cacheSignal</code> (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33557">#33557</a>)</li>
</ul>
<h3 dir="auto">React DOM</h3>
<ul dir="auto">
<li>Block on Suspensey Fonts during reveal of server-side-rendered
content (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33342">#33342</a>)</li>
<li>Use underscore instead of <code class="notranslate">:</code> for IDs
generated by <code class="notranslate">useId</code> (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>,
<a href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a>: <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32001">#32001</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33342">#33342</a><a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33099">#33099</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33422">#33422</a>)</li>
<li>Stop warning when ARIA 1.3 attributes are used (<a
href="https://bounce.depfu.com/github.com/Abdul-Omira">@Abdul-Omira</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34264">#34264</a>)</li>
<li>Allow <code class="notranslate">nonce</code> to be used on hoistable
styles (<a
href="https://bounce.depfu.com/github.com/Andarist">@Andarist</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32461">#32461</a>)</li>
<li>Warn for using a React owned node as a Container if it also has text
content (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32774">#32774</a>)</li>
<li>s/HTML/text for for error messages if text hydration mismatches (<a
href="https://bounce.depfu.com/github.com/rickhanlonii">@rickhanlonii</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32763">#32763</a>)</li>
<li>Fix a bug with <code class="notranslate">React.use</code> inside
<code class="notranslate">React.lazy</code>-ed Component (<a
href="https://bounce.depfu.com/github.com/hi-ogawa">@hi-ogawa</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33941">#33941</a>)</li>
<li>Enable the <code class="notranslate">progressiveChunkSize</code>
option for server-side-rendering APIs (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33027">#33027</a>)</li>
<li>Fix a bug with deeply nested Suspense inside Suspense fallback when
server-side-rendering (<a
href="https://bounce.depfu.com/github.com/gnoff">@gnoff</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33467">#33467</a>)</li>
<li>Avoid hanging when suspending after aborting while rendering (<a
href="https://bounce.depfu.com/github.com/gnoff">@gnoff</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34192">#34192</a>)</li>
<li>Add Node Web Streams to server-side-rendering APIs for Node.js (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33475">#33475</a>)</li>
</ul>
<h3 dir="auto">React Server Components</h3>
<ul dir="auto">
<li>Preload <code class="notranslate">&lt;img&gt;</code> and <code
class="notranslate">&lt;link&gt;</code> using hints before they're
rendered (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34604">#34604</a>)</li>
<li>Log error if production elements are rendered during development (<a
href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34189">#34189</a>)</li>
<li>Fix a bug when returning a Temporary reference (e.g. a Client
Reference) from Server Functions (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34084">#34084</a>,
<a href="https://bounce.depfu.com/github.com/denk0403">@denk0403</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33761">#33761</a>)</li>
<li>Pass line/column to <code
class="notranslate">filterStackFrame</code> (<a
href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33707">#33707</a>)</li>
<li>Support Async Modules in Turbopack Server References (<a
href="https://bounce.depfu.com/github.com/lubieowoce">@lubieowoce</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34531">#34531</a>)</li>
<li>Add support for .mjs file extension in Webpack (<a
href="https://bounce.depfu.com/github.com/jennyscript">@jennyscript</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33028">#33028</a>)</li>
<li>Fix a wrong missing key warning (<a
href="https://bounce.depfu.com/github.com/unstubbable">@unstubbable</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34350">#34350</a>)</li>
<li>Make console log resolve in predictable order (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33665">#33665</a>)</li>
</ul>
<h3 dir="auto">React Reconciler</h3>
<ul dir="auto">
<li>
<a
href="https://bounce.depfu.com/github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L255-L261">createContainer</a>
and <a
href="https://bounce.depfu.com/github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L305-L312">createHydrationContainer</a>
had their parameter order adjusted after <code
class="notranslate">on*</code> handlers to account for upcoming
experimental APIs</li>
</ul>
<h2 dir="auto">eslint-plugin-react-hooks@6.1.0</h2>
<p dir="auto"><strong>Note:</strong> Version 6.0.0 was mistakenly
released and immediately deprecated and untagged on npm. This is the
first official 6.x major release and includes breaking changes.</p>
<ul dir="auto">
<li>
<strong>Breaking:</strong> Require Node.js 18 or newer. (<a
href="https://bounce.depfu.com/github.com/michaelfaith">@michaelfaith</a>
in <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32458">#32458</a>)</li>
<li>
<strong>Breaking:</strong> Flat config is now the default <code
class="notranslate">recommended</code> preset. Legacy config moved to
<code class="notranslate">recommended-legacy</code>. (<a
href="https://bounce.depfu.com/github.com/michaelfaith">@michaelfaith</a>
in <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32457">#32457</a>)</li>
<li>
<strong>New Violations:</strong> Disallow calling <code
class="notranslate">use</code> within try/catch blocks. (<a
href="https://bounce.depfu.com/github.com/poteto">@poteto</a> in <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34040">#34040</a>)</li>
<li>
<strong>New Violations:</strong> Disallow calling <code
class="notranslate">useEffectEvent</code> functions in arbitrary
closures. (<a
href="https://bounce.depfu.com/github.com/jbrown215">@jbrown215</a> in
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33544">#33544</a>)</li>
<li>Handle <code class="notranslate">React.useEffect</code> in addition
to <code class="notranslate">useEffect</code> in rules-of-hooks. (<a
href="https://bounce.depfu.com/github.com/Ayc0">@Ayc0</a> in <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34076">#34076</a>)</li>
<li>Added <code class="notranslate">react-hooks</code> settings config
option that to accept <code
class="notranslate">additionalEffectHooks</code> that are used across
exhaustive-deps and rules-of-hooks rules. (<a
href="https://bounce.depfu.com/github.com/jbrown215">@jbrown215</a>) in
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34497">#34497</a>
</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="02ef495809...ae74234eae">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>




#### ✳️ react-dom (19.1.1 → 19.2.0) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.2.0">19.2.0</a></h4>

<blockquote><p dir="auto">Below is a list of all new features, APIs, and
bug fixes.</p>
<p dir="auto">Read the <a
href="https://react.dev/blog/2025/10/01/react-19-2">React 19.2 release
post</a> for more information.</p>
<h2 dir="auto">New React Features</h2>
<ul dir="auto">
<li>
<a href="https://react.dev/reference/react/Activity"><code
class="notranslate">&lt;Activity&gt;</code></a>: A new API to hide and
restore the UI and internal state of its children.</li>
<li>
<a href="https://react.dev/reference/react/useEffectEvent"><code
class="notranslate">useEffectEvent</code></a> is a React Hook that lets
you extract non-reactive logic into an <a
href="https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event">Effect
Event</a>.</li>
<li>
<a href="https://react.dev/reference/react/cacheSignal"><code
class="notranslate">cacheSignal</code></a> (for RSCs) lets your know
when the <code class="notranslate">cache()</code> lifetime is over.</li>
<li>
<a
href="https://react.dev/reference/developer-tooling/react-performance-tracks">React
Performance tracks</a> appear on the Performance panel’s timeline in
your browser developer tools</li>
</ul>
<h2 dir="auto">New React DOM Features</h2>
<ul dir="auto">
<li>Added resume APIs for partial pre-rendering with Web Streams:
<ul dir="auto">
<li>
<a href="https://react.dev/reference/react-dom/server/resume"><code
class="notranslate">resume</code></a>: to resume a prerender to a
stream.</li>
<li>
<a
href="https://react.dev/reference/react-dom/static/resumeAndPrerender"><code
class="notranslate">resumeAndPrerender</code></a>: to resume a prerender
to HTML.</li>
</ul>
</li>
<li>Added resume APIs for partial pre-rendering with Node Streams:
<ul dir="auto">
<li>
<a
href="https://react.dev/reference/react-dom/server/resumeToPipeableStream"><code
class="notranslate">resumeToPipeableStream</code></a>: to resume a
prerender to a stream.</li>
<li>
<a
href="https://react.dev/reference/react-dom/static/resumeAndPrerenderToNodeStream"><code
class="notranslate">resumeAndPrerenderToNodeStream</code></a>: to resume
a prerender to HTML.</li>
</ul>
</li>
<li>Updated <a
href="https://react.dev/reference/react-dom/static/prerender"><code
class="notranslate">prerender</code></a> APIs to return a <code
class="notranslate">postponed</code> state that can be passed to the
<code class="notranslate">resume</code> APIs.</li>
</ul>
<h2 dir="auto">Notable changes</h2>
<ul dir="auto">
<li>React DOM now batches suspense boundary reveals, matching the
behavior of client side rendering. This change is especially noticeable
when animating the reveal of Suspense boundaries e.g. with the upcoming
<code class="notranslate">&lt;ViewTransition&gt;</code> Component. React
will batch as much reveals as possible before the first paint while
trying to hit popular first-contentful paint metrics.</li>
<li>Add Node Web Streams (<code class="notranslate">prerender</code>,
<code class="notranslate">renderToReadableStream</code>) to
server-side-rendering APIs for Node.js</li>
<li>Use underscore instead of <code class="notranslate">:</code> IDs
generated by useId</li>
</ul>
<h2 dir="auto">All Changes</h2>
<h3 dir="auto">React</h3>
<ul dir="auto">
<li>
<code class="notranslate">&lt;Activity /&gt;</code> was developed over
many years, starting before <code
class="notranslate">ClassComponent.setState</code> (<a
href="https://bounce.depfu.com/github.com/acdlite">@acdlite</a> <a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
and many others)</li>
<li>Stringify context as "SomeContext" instead of "SomeContext.Provider"
(<a href="https://bounce.depfu.com/github.com/kassens">@kassens</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33507">#33507</a>)</li>
<li>Include stack of cause of React instrumentation errors with <code
class="notranslate">%o</code> placeholder (<a
href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34198">#34198</a>)</li>
<li>Fix infinite <code class="notranslate">useDeferredValue</code> loop
in popstate event (<a
href="https://bounce.depfu.com/github.com/acdlite">@acdlite</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32821">#32821</a>)</li>
<li>Fix a bug when an initial value was passed to <code
class="notranslate">useDeferredValue</code> (<a
href="https://bounce.depfu.com/github.com/acdlite">@acdlite</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34376">#34376</a>)</li>
<li>Fix a crash when submitting forms with Client Actions (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33055">#33055</a>)</li>
<li>Hide/unhide the content of dehydrated suspense boundaries if they
resuspend (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32900">#32900</a>)</li>
<li>Avoid stack overflow on wide trees during Hot Reload (<a
href="https://bounce.depfu.com/github.com/sophiebits">@sophiebits</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34145">#34145</a>)</li>
<li>Improve Owner and Component stacks in various places (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>,
<a href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a>: <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33629">#33629</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33724">#33724</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32735">#32735</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33723">#33723</a>)</li>
<li>Add <code class="notranslate">cacheSignal</code> (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33557">#33557</a>)</li>
</ul>
<h3 dir="auto">React DOM</h3>
<ul dir="auto">
<li>Block on Suspensey Fonts during reveal of server-side-rendered
content (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33342">#33342</a>)</li>
<li>Use underscore instead of <code class="notranslate">:</code> for IDs
generated by <code class="notranslate">useId</code> (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>,
<a href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a>: <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32001">#32001</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33342">#33342</a><a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33099">#33099</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33422">#33422</a>)</li>
<li>Stop warning when ARIA 1.3 attributes are used (<a
href="https://bounce.depfu.com/github.com/Abdul-Omira">@Abdul-Omira</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34264">#34264</a>)</li>
<li>Allow <code class="notranslate">nonce</code> to be used on hoistable
styles (<a
href="https://bounce.depfu.com/github.com/Andarist">@Andarist</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32461">#32461</a>)</li>
<li>Warn for using a React owned node as a Container if it also has text
content (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32774">#32774</a>)</li>
<li>s/HTML/text for for error messages if text hydration mismatches (<a
href="https://bounce.depfu.com/github.com/rickhanlonii">@rickhanlonii</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32763">#32763</a>)</li>
<li>Fix a bug with <code class="notranslate">React.use</code> inside
<code class="notranslate">React.lazy</code>-ed Component (<a
href="https://bounce.depfu.com/github.com/hi-ogawa">@hi-ogawa</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33941">#33941</a>)</li>
<li>Enable the <code class="notranslate">progressiveChunkSize</code>
option for server-side-rendering APIs (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33027">#33027</a>)</li>
<li>Fix a bug with deeply nested Suspense inside Suspense fallback when
server-side-rendering (<a
href="https://bounce.depfu.com/github.com/gnoff">@gnoff</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33467">#33467</a>)</li>
<li>Avoid hanging when suspending after aborting while rendering (<a
href="https://bounce.depfu.com/github.com/gnoff">@gnoff</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34192">#34192</a>)</li>
<li>Add Node Web Streams to server-side-rendering APIs for Node.js (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33475">#33475</a>)</li>
</ul>
<h3 dir="auto">React Server Components</h3>
<ul dir="auto">
<li>Preload <code class="notranslate">&lt;img&gt;</code> and <code
class="notranslate">&lt;link&gt;</code> using hints before they're
rendered (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34604">#34604</a>)</li>
<li>Log error if production elements are rendered during development (<a
href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34189">#34189</a>)</li>
<li>Fix a bug when returning a Temporary reference (e.g. a Client
Reference) from Server Functions (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34084">#34084</a>,
<a href="https://bounce.depfu.com/github.com/denk0403">@denk0403</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33761">#33761</a>)</li>
<li>Pass line/column to <code
class="notranslate">filterStackFrame</code> (<a
href="https://bounce.depfu.com/github.com/eps1lon">@eps1lon</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33707">#33707</a>)</li>
<li>Support Async Modules in Turbopack Server References (<a
href="https://bounce.depfu.com/github.com/lubieowoce">@lubieowoce</a> <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34531">#34531</a>)</li>
<li>Add support for .mjs file extension in Webpack (<a
href="https://bounce.depfu.com/github.com/jennyscript">@jennyscript</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33028">#33028</a>)</li>
<li>Fix a wrong missing key warning (<a
href="https://bounce.depfu.com/github.com/unstubbable">@unstubbable</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34350">#34350</a>)</li>
<li>Make console log resolve in predictable order (<a
href="https://bounce.depfu.com/github.com/sebmarkbage">@sebmarkbage</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33665">#33665</a>)</li>
</ul>
<h3 dir="auto">React Reconciler</h3>
<ul dir="auto">
<li>
<a
href="https://bounce.depfu.com/github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L255-L261">createContainer</a>
and <a
href="https://bounce.depfu.com/github.com/facebook/react/blob/v19.2.0/packages/react-reconciler/src/ReactFiberReconciler.js#L305-L312">createHydrationContainer</a>
had their parameter order adjusted after <code
class="notranslate">on*</code> handlers to account for upcoming
experimental APIs</li>
</ul>
<h2 dir="auto">eslint-plugin-react-hooks@6.1.0</h2>
<p dir="auto"><strong>Note:</strong> Version 6.0.0 was mistakenly
released and immediately deprecated and untagged on npm. This is the
first official 6.x major release and includes breaking changes.</p>
<ul dir="auto">
<li>
<strong>Breaking:</strong> Require Node.js 18 or newer. (<a
href="https://bounce.depfu.com/github.com/michaelfaith">@michaelfaith</a>
in <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32458">#32458</a>)</li>
<li>
<strong>Breaking:</strong> Flat config is now the default <code
class="notranslate">recommended</code> preset. Legacy config moved to
<code class="notranslate">recommended-legacy</code>. (<a
href="https://bounce.depfu.com/github.com/michaelfaith">@michaelfaith</a>
in <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32457">#32457</a>)</li>
<li>
<strong>New Violations:</strong> Disallow calling <code
class="notranslate">use</code> within try/catch blocks. (<a
href="https://bounce.depfu.com/github.com/poteto">@poteto</a> in <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34040">#34040</a>)</li>
<li>
<strong>New Violations:</strong> Disallow calling <code
class="notranslate">useEffectEvent</code> functions in arbitrary
closures. (<a
href="https://bounce.depfu.com/github.com/jbrown215">@jbrown215</a> in
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33544">#33544</a>)</li>
<li>Handle <code class="notranslate">React.useEffect</code> in addition
to <code class="notranslate">useEffect</code> in rules-of-hooks. (<a
href="https://bounce.depfu.com/github.com/Ayc0">@Ayc0</a> in <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34076">#34076</a>)</li>
<li>Added <code class="notranslate">react-hooks</code> settings config
option that to accept <code
class="notranslate">additionalEffectHooks</code> that are used across
exhaustive-deps and rules-of-hooks rules. (<a
href="https://bounce.depfu.com/github.com/jbrown215">@jbrown215</a>) in
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/34497">#34497</a>
</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="02ef495809...ae74234eae">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-10-09 14:33:23 -04:00
Robin Malfait
01d1e98259
Canonicalization constant folding and handling zeros (#19095)
The main goal of this PR was to support canonicalization of zero like
values. We essentially want to canonicalize `-mt-0` as `mt-0`, but also
`mt-[0px]`, `mt-[0rem]`, and other length-like units to just `mt-0`.

To do this, we had to handle 2 things:

1. We introduced some more constant folding, including making `0px` and
`0rem` fold to `0`. We only do this for length units. We also normalize
`-0`, `+0`, `-0.0` and so on to `0`.
2. While pre-computing utilities in our lookup table, we make sure that
we prefer `mt-0` over `-mt-0` if both result in the same signature.

Moved some of the constant folding logic into its own function and added
a bunch of separate tests for it.

## Test plan

Added more unit tests where we normalize different zero-like values to
`0`.

Running the canonicalization logic:
```js
designSystem.canonicalizeCandidates([
  '-m-0',
  '-m-[-0px]',
  '-m-[-0rem]',
  '-m-[0px]',
  '-m-[0rem]',
  'm-0',
  'm-[-0px]',
  'm-[-0rem]',
  'm-[0px]',
  'm-[0rem]',
  'm-[calc(var(--spacing)*0)]',
  'm-[--spacing(0)]',
  'm-[--spacing(0.0)]',
  'm-[+0]',
  'm-[-0]',
  '-m-[-0]',
  '-m-[+0]',
]) // → ['m-0']
```

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-10-09 18:09:29 +00:00
Robin Malfait
3aadba7cc1
Move modifier of not-*, has-*, and in-* variant to sub variant (#19100)
This PR fixes an issue where a compound variant with a modifier such as
`not-group-hover/name:flex` would not generate anything because the
`/name` modifier belongs to the `not` variant, and not the compounded
`group-hover` variant.

This PR is a **workaround** (and definitely not perfect) by special
casing the `not`, `has`, and `in` variants such that their modifiers are
moved internally to the sub variant as-if the `/name` existed on
`group-hover`.

We don't do it for other compound variants such as `group` and `peer`
because then `group-peer-focus/name:underline` would result in a
breaking change:

```diff
- .group-peer-focus\\/name\\:flex:is(:where(.group\\/name):is(:where(.peer):focus ~ *) *)
+ .group-peer-focus\/name\:flex:is(:where(.group):is(:where(.peer\/name):focus ~ *) *) 
```

In case the diff is not clear, the name has moved:
<img width="1219" height="78" alt="image"
src="https://github.com/user-attachments/assets/dce7bc95-9d93-452d-a275-b3891a05a1a4"
/>


This is also a limited workaround, because if you need multiple
modifiers it won't work. I would've loved to special case this _inside_
the `not`, `has`, and `in` code that handles these variants, but we
handle the variants in a depth-first way, so by the time you are
handling the `not` variant, the sub variant was already handled...

In a perfect world, you can use something like `not-group/name-hover`
but then it becomes unambiguous because is `name` the name, is
`name-hover`?

## Test plan

Added a new test that wouldn't generate anything before this fix.

Fixes: #15772
2025-10-09 19:30:01 +02:00
Robin Malfait
0c14df1a15
Fix resolving colors via theme(…) in compat mode with nested objects (#19097)
This PR fixes an issue when loading (nested) colors from a config file
and later referencing it via the `theme(…)` function in CSS.

Given a config like this:

```js
module.exports = {
  theme: {
    colors: {
      foo: 'var(--foo-foo)',
      'foo-bar': 'var(--foo-foo-bar)',
    },
  },
}
```

We internally map this into the design system. The issue here is that
the `foo` and `foo-bar` are overlapping and it behaves more like this:

```js
{
  foo: {
    DEFAULT: 'var(--foo-foo)',
    bar: 'var(--foo-foo-bar)'
  },
}
```

So while we can easily resolve `colors.foo-bar`, the `colors.foo` would
result in the object with a `DEFAULT` key. This PR solves that by using
the `DEFAULT` key if we end up with an object that has it.

If you end up resolving an object (`theme(colors)`) then the behavior is
unchanged.


## Test plan

1. Added a test based on the config in the issue (which failed before
this fix).
2. Also simplified the test case after identifying the problem (with the
`DEFAULT` key).

Fixes: #19091
2025-10-09 17:10:32 +00:00
Robin Malfait
561983d7e5
Suppress warnings when using :deep, :slotted and :global (#19094)
This PR ignores warnings related to `:deep`, `:slotted` and `:global`
used by frameworks like Vue (see:
https://vuejs.org/api/sfc-css-features#deep-selectors).

## Test plan

Used a `:deep()` selector in a test project (Catalyst).

```diff
diff --git a/templates/catalyst/src/tailwind.css b/templates/catalyst/src/tailwind.css
index 79887e67..2acb749c 100644
--- a/templates/catalyst/src/tailwind.css
+++ b/templates/catalyst/src/tailwind.css
@@ -9,3 +9,7 @@
   --font-sans: Inter, sans-serif;
   --font-sans--font-feature-settings: 'cv11';
 }
+
+:deep(.foo) {
+  color: red;
+}
```

Before:

<img width="1625" height="372" alt="image"
src="https://github.com/user-attachments/assets/4b948080-1aeb-41ba-8268-98828da21768"
/>


After:

<img width="717" height="114" alt="image"
src="https://github.com/user-attachments/assets/b8668da2-693e-4010-99fb-de3bc4a47bf9"
/>


Fixes:
https://github.com/tailwindlabs/tailwindcss/pull/18918#issuecomment-3384928613
2025-10-09 11:58:51 +02:00
Robin Malfait
0c8d881f0e
Improve percentage canonicalization (#19072)
This PR improves the canonicalization of percentage values such that
`[.1]`, `[.10]`, `[10%]` and `[10.0%]` are all treated as the same
value.

Right now we're only focusing on percentages. We can likely do this for
all numbers, but I'm a little afraid of places where you can have
multiple numbers separated by multiple dots (think SVGs).

## Test plan

1. Added more tests to cover the new cases.
2. Tested it in a local test project, where you can see the
normalization in action.

<img width="1383" height="117" alt="image"
src="https://github.com/user-attachments/assets/03d99e3a-4404-437b-b458-58f7e8ce60da"
/>
2025-10-07 09:47:22 -04:00
Robin Malfait
efe084b7e7
Improve performance of cloning AST nodes (#19067)
This PR improves the performance of when we need to clone some AST
nodes. We have a few places where we clone `Candidate`, `Variant` and
CSS `AST` nodes.

Right now we use `structuredClone`, which works, but it is a generic
solution. However, we do know the exact structure of these AST nodes, so
we can write specialized clone functions that are much faster.

## Test plan

1. All the tests still pass with this change
2. The performance is better:

```
  cloneCandidate - src/candidate.bench.ts > Candidate cloning
    1.72x faster than cloneCandidate (spread)
    74.03x faster than structuredClone

  cloneAstNode() - src/ast.bench.ts > Cloning AST nodes
    1.15x faster than cloneAstNode (with spread)
    33.54x faster than structuredClone()
```

Ready for review, but should be merged after #19059
2025-10-07 10:46:37 +00:00
Robin Malfait
b77971f754
Introduce canonicalizeCandidates on the internal Design System (#19059)
This PR introduces a new `canonicalizeCandidates` function on the
internal Design System.

The big motivation to moving this to the core `tailwindcss` package is
that we can use this in various places:

- The Raycast extension
- The VS Code extension / language server
- 3rd party tools that use the Tailwind CSS design system APIs

> This PR looks very big, but **I think it's best to go over the changes
commit by commit**. Basically all of these steps already existed in the
upgrade tool, but are now moved to our core `tailwindcss` package.

Here is a list of all the changes:
- Added a new `canonicalizeCandidates` function to the design system
- Moved various migration steps to the core package. I inlined them in
the same file and because of that I noticed a specific pattern (more on
this later).
- Moved `printCandidate` tests to the `tailwindcss` package
- Setup tests for `canonicalizeCandidates` based on the existing tests
in the upgrade tool.

I noticed that all the migrations followed a specific pattern:

1. Parse the raw candidate into a `Candidate[]` AST
2. In a loop, try to migrate the `Candidate` to a new `Candidate` (this
often handled both the `Candidate` and its `Variant[]`)
3. If something changed, print the new `Candidate` back to a string, and
pass it to the next migration step.

While this makes sense in isolation, we are doing a lot of repeated work
by parsing, modifying, and printing the candidate multiple times. This
let me to introduce the `big refactor` commit. This changes the steps
to:

1. Up front, parse the raw candidate into a `Candidate[]` _once_.
2. Strip the variants and the important marker from the candidate. This
means that each migration step only has to deal with the base `utility`
and not care about the variants or the important marker. We can
re-attach these afterwards.
3. Instead of a `rawCandidate: string`, each migration step receives an
actual `Candidate` object (or a `Variant` object).
4. I also split up the migration steps for the `Candidate` and the
`Variant[]`.

All of this means that there is a lot less work that needs to be done.
We can also cache results between migrations. So `[@media_print]:flex`
and `[@media_print]:block` will result in `print:flex` and `print:block`
respectively, but the `[@media_print]` part is only migrated once across
both candidates.

One migration step relied on the `postcss-selector-parser` package to
parse selectors and attribute selectors. I didn't want to introduce a
package just for this, so instead used our own `SelectorParser` in the
migration and wrote a small `AttributeSelectorParser` that can parse the
attribute selector into a little data structure we can work with
instead.

If we want, we can split this PR up into smaller pieces, but since the
biggest chunk is moving existing code around, I think it's fairly doable
to review as long as you go commit by commit.

---

With this new API, we can turn:
```
[
  'bg-red-500',
  'hover:bg-red-500',
  '[@media_print]:bg-red-500',
  'hover:[@media_print]:bg-red-500',
  'bg-red-500/100',
  'hover:bg-red-500/100',
  '[@media_print]:bg-red-500/100',
  'hover:[@media_print]:bg-red-500/100',
  'bg-[var(--color-red-500)]',
  'hover:bg-[var(--color-red-500)]',
  '[@media_print]:bg-[var(--color-red-500)]',
  'hover:[@media_print]:bg-[var(--color-red-500)]',
  'bg-[var(--color-red-500)]/100',
  'hover:bg-[var(--color-red-500)]/100',
  '[@media_print]:bg-[var(--color-red-500)]/100',
  'hover:[@media_print]:bg-[var(--color-red-500)]/100',
  'bg-(--color-red-500)',
  'hover:bg-(--color-red-500)',
  '[@media_print]:bg-(--color-red-500)',
  'hover:[@media_print]:bg-(--color-red-500)',
  'bg-(--color-red-500)/100',
  'hover:bg-(--color-red-500)/100',
  '[@media_print]:bg-(--color-red-500)/100',
  'hover:[@media_print]:bg-(--color-red-500)/100',
  'bg-[color:var(--color-red-500)]',
  'hover:bg-[color:var(--color-red-500)]',
  '[@media_print]:bg-[color:var(--color-red-500)]',
  'hover:[@media_print]:bg-[color:var(--color-red-500)]',
  'bg-[color:var(--color-red-500)]/100',
  'hover:bg-[color:var(--color-red-500)]/100',
  '[@media_print]:bg-[color:var(--color-red-500)]/100',
  'hover:[@media_print]:bg-[color:var(--color-red-500)]/100',
  'bg-(color:--color-red-500)',
  'hover:bg-(color:--color-red-500)',
  '[@media_print]:bg-(color:--color-red-500)',
  'hover:[@media_print]:bg-(color:--color-red-500)',
  'bg-(color:--color-red-500)/100',
  'hover:bg-(color:--color-red-500)/100',
  '[@media_print]:bg-(color:--color-red-500)/100',
  'hover:[@media_print]:bg-(color:--color-red-500)/100',
  '[background-color:var(--color-red-500)]',
  'hover:[background-color:var(--color-red-500)]',
  '[@media_print]:[background-color:var(--color-red-500)]',
  'hover:[@media_print]:[background-color:var(--color-red-500)]',
  '[background-color:var(--color-red-500)]/100',
  'hover:[background-color:var(--color-red-500)]/100',
  '[@media_print]:[background-color:var(--color-red-500)]/100',
  'hover:[@media_print]:[background-color:var(--color-red-500)]/100'
]
```

Into their canonicalized form:
```
[
  'bg-red-500',
  'hover:bg-red-500',
  'print:bg-red-500',
  'hover:print:bg-red-500'
]
```

The list is also unique, so we won't end up with `bg-red-500 bg-red-500`
twice.

While the canonicalization itself is fairly fast, we still pay a **~1s**
startup cost for some migrations (once, and cached for the entire
lifetime of the design system). I would like to keep improving the
performance and the kinds of migrations we do, but I think this is a
good start.

The cost we pay is for:

1. Generating a full list of all possible utilities based on the
`getClassList` suggestions API.
2. Generating a full list of all possible variants.

The canonicalization step for this list takes **~2.9ms** on my machine.

Just for fun, if you use the `getClassList` API for intellisense that
generates all the suggestions and their modifiers, you get a list of
**263788** classes. If you canonicalize all of these, it takes
**~500ms** in total. So roughly **~1.9μs** per candidate.

This new API doesn't result in a performance difference for normal
Tailwind CSS builds.

The other potential concern is file size of the package. The generated
`tailwindcss.tgz` file changed like this:
```diff
- 684652 bytes (684.65 kB)
+ 749169 bytes (749.17 kB)
```
So the package increased by ~65 kB which I don't think is the end of the
world, but it is important for the `@tailwindcss/browser` build which we
don't want to grow unnecessarily. For this reason we remove some of the
code for the design system conditionally such that you don't pay this
cost in an environment where you will never need this API.

The `@tailwindcss/browser` build looks like this:
```shell
`dist/index.global.js 255.14 KB` (main)
`dist/index.global.js 272.61 KB` (before this change)
`dist/index.global.js 252.83 KB` (after this change, even smaller than on `main`)
```
2025-10-07 12:41:12 +02:00
Robin Malfait
73628f69f3
Fix Safari devtools issues because of nested @supports at-rules without normal rule (#19069)
This PR fixes a weird Safari rendering bug in the devtools. This seems
to be happening when using `@supports`, especially nested `@supports`
at-rules.

The issue is that our color-mix fallback generates declarations directly
in `@supports` at-rules which causes the weird rendering bug in Safari.

Adding this intermediate `&` rule seems to fix the issue.

This is a workaround for a browser bug, but the additional 3 characters
shouldn't be the end of the world.

## Test plan

1. Updated the tests with the new `& { }` intermediate rule
2. Other tests still pass as expected

| Before | After |
| --- | --- |
| <img width="450" height="549" alt="image"
src="https://github.com/user-attachments/assets/4b51fb93-8073-4414-8139-dec75e6bc086"
/> | <img width="448" height="548" alt="image"
src="https://github.com/user-attachments/assets/1016af67-c1eb-43dc-9554-158e7e2264c4"
/> |

Fixes: #19065

[ci-all]
2025-10-06 20:04:40 +02:00
Robin Malfait
49948d087e
Fix failing integration tests (#19068)
The vite/nuxt integration tests started failing because one of the
internal dependencies (`nuxi`) was updated from `3.28.0` to `3.29.0`
which includes a newer version of `undici` which in turn relies on
`node:sqlite`.

`node:sqlite` was added in a newer Node version, and we still use Node
v20 in CI.

This PR pins `nuxi` to `3.28.0` until we can upgrade our Node version in
CI.

[ci-all]
2025-10-06 15:46:25 +00:00
Robin Malfait
86e4b8e636
run prettier 2025-10-06 17:15:57 +02:00
Philipp Spiess
231e17b84a Change permissions so issue-triage can run for everyone 2025-10-06 16:20:09 +02:00
Philipp Spiess
4cbdfa6e43 Issue triage tweaks 2025-10-06 12:37:20 +02:00
Philipp Spiess
9ef314e786
Add agentic workflow issue-triage (#19057)
This PR attempts to set up an automatic issue triaging tool using Claude
Code as the agent and GitHub Agentics as the framework:
https://githubnext.github.io/gh-aw/start-here/quick-start/

Based on the `safe-outputs` setting, this agent can only label issues or
write replies. Let's see how useful that is 🤷

You can't run it locally since it does not have a manual trigger and
needs a new github issue instead so my plan is to merge it and then
create some super legit issues to see if it works!

## Test plan

```
α dev/tailwindcss (add-workflow-workflows-issue-triage.md-3051) gh aw status
Workflow Status
Name         | Installed | Up-to-date | Status  | Time Remaining
------------ | --------- | ---------- | ------- | --------------
issue-triage | Yes       | Yes        | Unknown | 29d 22h
```

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-10-06 12:24:51 +02:00
depfu[bot]
6802c685c9
Update globby 14.1.0 → 15.0.0 (major) (#19056)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ globby (14.1.0 → 15.0.0) ·
[Repo](https://github.com/sindresorhus/globby)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/sindresorhus/globby/releases/tag/v15.0.0">15.0.0</a></h4>

<blockquote><h3 dir="auto">Breaking</h3>
<ul dir="auto">
<li>Require Node.js 20 <a
href="4ae42c8bbd"><tt>4ae42c8</tt></a>
</li>
</ul>
<h3 dir="auto">Fixes</h3>
<ul dir="auto">
<li>Fix <code class="notranslate">expandDirectories</code> not working
with globstar patterns <a
href="98d691ad73"><tt>98d691a</tt></a>
</li>
<li>Fix relative paths with gitignore option <a
href="b4d78d8039"><tt>b4d78d8</tt></a>
</li>
<li>Fix gitignore patterns in subdirectories not applying recursively <a
href="2cb6088a0a"><tt>2cb6088</tt></a>
</li>
<li>Fix TypeScript types for <code
class="notranslate">globbyStream</code> to correctly yield strings <a
href="1c7f3ed116"><tt>1c7f3ed</tt></a>
</li>
</ul>
<hr>
<p dir="auto"><a
href="https://bounce.depfu.com/github.com/sindresorhus/globby/compare/v14.1.0...v15.0.0"><tt>v14.1.0...v15.0.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="60d7de5447...b65747ec17">See
the full diff on Github</a>. The new version differs by 7 commits:</p>
<ul>
<li><a
href="b65747ec17"><code>15.0.0</code></a></li>
<li><a
href="4ae42c8bbd"><code>Require
Node.js 20</code></a></li>
<li><a
href="98d691ad73"><code>Fix
`expandDirectories` not working with globstar patterns</code></a></li>
<li><a
href="b4d78d8039"><code>Fix
relative paths with gitignore option</code></a></li>
<li><a
href="2cb6088a0a"><code>Fix
gitignore patterns in subdirectories not applying
recursively</code></a></li>
<li><a
href="0a4df0eeed"><code>Document
performance implications of `gitignore` option</code></a></li>
<li><a
href="1c7f3ed116"><code>Fix
TypeScript types for `globbyStream` to correctly yield
strings</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-06 11:14:31 +02:00
depfu[bot]
573f633f13
Update eslint 9.35.0 → 9.36.0 (minor) (#19049)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.35.0 → 9.36.0) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.36.0">9.36.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="47afcf668d"><code
class="notranslate">47afcf6</code></a> feat: correct <code
class="notranslate">preserve-caught-error</code> edge cases (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20109">#20109</a>)
(Francesco Trotta)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="75b74d865d"><code
class="notranslate">75b74d8</code></a> fix: add missing rule option
types (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20127">#20127</a>)
(ntnyq)</li>
<li>
<a
href="1c0d85049e"><code
class="notranslate">1c0d850</code></a> fix: update <code
class="notranslate">eslint-all.js</code> to use <code
class="notranslate">Object.freeze</code> for <code
class="notranslate">rules</code> object (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20116">#20116</a>)
(루밀LuMir)</li>
<li>
<a
href="7d61b7fadc"><code
class="notranslate">7d61b7f</code></a> fix: add missing scope types to
<code class="notranslate">Scope.type</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20110">#20110</a>)
(Pixel998)</li>
<li>
<a
href="7a670c301b"><code
class="notranslate">7a670c3</code></a> fix: correct rule option typings
in <code class="notranslate">rules.d.ts</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20084">#20084</a>)
(Pixel998)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="b73ab12acd"><code
class="notranslate">b73ab12</code></a> docs: update examples to use
<code class="notranslate">defineConfig</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20131">#20131</a>)
(sethamus)</li>
<li>
<a
href="31d9392699"><code
class="notranslate">31d9392</code></a> docs: fix typos (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20118">#20118</a>)
(Pixel998)</li>
<li>
<a
href="c7f861b3f8"><code
class="notranslate">c7f861b</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="6b0c08b106"><code
class="notranslate">6b0c08b</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="91f97c5046"><code
class="notranslate">91f97c5</code></a> docs: Update README (GitHub
Actions Bot)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="12411e8d45"><code
class="notranslate">12411e8</code></a> chore: upgrade @eslint/js@9.36.0
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20139">#20139</a>)
(Milos Djermanovic)</li>
<li>
<a
href="488cba6b39"><code
class="notranslate">488cba6</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="bac82a2a9c"><code
class="notranslate">bac82a2</code></a> ci: simplify renovate
configuration (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19907">#19907</a>)
(唯然)</li>
<li>
<a
href="c00bb37d62"><code
class="notranslate">c00bb37</code></a> ci: bump actions/labeler from 5
to 6 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20090">#20090</a>)
(dependabot[bot])</li>
<li>
<a
href="fee751dc8a"><code
class="notranslate">fee751d</code></a> refactor: use <code
class="notranslate">defaultOptions</code> in rules (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20121">#20121</a>)
(Pixel998)</li>
<li>
<a
href="1ace67d9f7"><code
class="notranslate">1ace67d</code></a> chore: update example to use
<code class="notranslate">defineConfig</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20111">#20111</a>)
(루밀LuMir)</li>
<li>
<a
href="4821963bf7"><code
class="notranslate">4821963</code></a> test: add missing loc information
to error objects in rule tests (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20112">#20112</a>)
(루밀LuMir)</li>
<li>
<a
href="b42c42e7cd"><code
class="notranslate">b42c42e</code></a> chore: disallow use of deprecated
<code class="notranslate">type</code> property in core rule tests (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20094">#20094</a>)
(Milos Djermanovic)</li>
<li>
<a
href="7bb498d720"><code
class="notranslate">7bb498d</code></a> test: remove deprecated <code
class="notranslate">type</code> property from core rule tests (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20093">#20093</a>)
(Pixel998)</li>
<li>
<a
href="e10cf2ab42"><code
class="notranslate">e10cf2a</code></a> ci: bump actions/setup-node from
4 to 5 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20089">#20089</a>)
(dependabot[bot])</li>
<li>
<a
href="5cb0ce48ef"><code
class="notranslate">5cb0ce4</code></a> refactor: use <code
class="notranslate">meta.defaultOptions</code> in <code
class="notranslate">preserve-caught-error</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20080">#20080</a>)
(Pixel998)</li>
<li>
<a
href="f9f7cb578d"><code
class="notranslate">f9f7cb5</code></a> chore: package.json update for
eslint-config-eslint release (Jenkins)</li>
<li>
<a
href="81764b2980"><code
class="notranslate">81764b2</code></a> chore: update <code
class="notranslate">eslint</code> peer dependency in <code
class="notranslate">eslint-config-eslint</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20079">#20079</a>)
(Milos Djermanovic)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="8401101d1e...b4857e54e5">See
the full diff on Github</a>. The new version differs by 25 commits:</p>
<ul>
<li><a
href="b4857e54e5"><code>9.36.0</code></a></li>
<li><a
href="5878a4243f"><code>Build:
changelog update for 9.36.0</code></a></li>
<li><a
href="12411e8d45"><code>chore:
upgrade @eslint/js@9.36.0 (#20139)</code></a></li>
<li><a
href="488cba6b39"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="b73ab12acd"><code>docs:
update examples to use `defineConfig` (#20131)</code></a></li>
<li><a
href="47afcf668d"><code>feat:
correct `preserve-caught-error` edge cases (#20109)</code></a></li>
<li><a
href="75b74d865d"><code>fix:
add missing rule option types (#20127)</code></a></li>
<li><a
href="bac82a2a9c"><code>ci:
simplify renovate configuration (#19907)</code></a></li>
<li><a
href="1c0d85049e"><code>fix:
update `eslint-all.js` to use `Object.freeze` for `rules` object
(#20116)</code></a></li>
<li><a
href="c00bb37d62"><code>ci:
bump actions/labeler from 5 to 6 (#20090)</code></a></li>
<li><a
href="fee751dc8a"><code>refactor:
use `defaultOptions` in rules (#20121)</code></a></li>
<li><a
href="31d9392699"><code>docs:
fix typos (#20118)</code></a></li>
<li><a
href="7d61b7fadc"><code>fix:
add missing scope types to `Scope.type` (#20110)</code></a></li>
<li><a
href="1ace67d9f7"><code>chore:
update example to use `defineConfig` (#20111)</code></a></li>
<li><a
href="4821963bf7"><code>test:
add missing loc information to error objects in rule tests
(#20112)</code></a></li>
<li><a
href="c7f861b3f8"><code>docs:
Update README</code></a></li>
<li><a
href="6b0c08b106"><code>docs:
Update README</code></a></li>
<li><a
href="7a670c301b"><code>fix:
correct rule option typings in `rules.d.ts` (#20084)</code></a></li>
<li><a
href="b42c42e7cd"><code>chore:
disallow use of deprecated `type` property in core rule tests
(#20094)</code></a></li>
<li><a
href="7bb498d720"><code>test:
remove deprecated `type` property from core rule tests
(#20093)</code></a></li>
<li><a
href="91f97c5046"><code>docs:
Update README</code></a></li>
<li><a
href="e10cf2ab42"><code>ci:
bump actions/setup-node from 4 to 5 (#20089)</code></a></li>
<li><a
href="5cb0ce48ef"><code>refactor:
use `meta.defaultOptions` in `preserve-caught-error`
(#20080)</code></a></li>
<li><a
href="f9f7cb578d"><code>chore:
package.json update for eslint-config-eslint release</code></a></li>
<li><a
href="81764b2980"><code>chore:
update `eslint` peer dependency in `eslint-config-eslint`
(#20079)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-10-03 12:35:07 -04:00
Jordan Pittman
b67cbcf6cc
Prepare v4.1.14 release (#19037)
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-10-01 11:48:02 -04:00
depfu[bot]
d96a48e3c1 Update all of nextjs to version 15.5.4 2025-09-30 19:21:14 +00:00
depfu[bot]
2766a49e28
Update tar 7.4.3 → 7.5.1 (minor) (#19035)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ tar (7.4.3 → 7.5.1) · [Repo](https://github.com/isaacs/node-tar)
· [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)




<details>
<summary>Commits</summary>
<p><a
href="206fcf91b0...64728e88cb">See
the full diff on Github</a>. The new version differs by 21 commits:</p>
<ul>
<li><a
href="64728e88cb"><code>7.5.1</code></a></li>
<li><a
href="5330eb04bc"><code>fix:
consistent TOCTOU behavior in sync t.list</code></a></li>
<li><a
href="dcb0287d7d"><code>7.5.0</code></a></li>
<li><a
href="aa1bed9287"><code>changelog
7.5</code></a></li>
<li><a
href="49bf8f9926"><code>feat:
add initial zstd support</code></a></li>
<li><a
href="b35ff94748"><code>ci:
don&#39;t bother testing on node 18</code></a></li>
<li><a
href="181be3a985"><code>update
workflows, tshy configs</code></a></li>
<li><a
href="15d4510393"><code>7.4.4</code></a></li>
<li><a
href="a4744652c3"><code>Fix
some typos</code></a></li>
<li><a
href="65ff5bed15"><code>docs(changelog):
add missing v7 breaking change</code></a></li>
<li><a
href="458d0fd236"><code>docs:
clean up duplicate onwarn options</code></a></li>
<li><a
href="afa8cf5e53"><code>update
all deps</code></a></li>
<li><a
href="bbfcec2e8b"><code>minizlib@3.1.0</code></a></li>
<li><a
href="cdd4f7dced"><code>doc:
fix lowlevel tar.Parser typo</code></a></li>
<li><a
href="eafc4b8454"><code>remove
yallist from parse, but NOT from pack</code></a></li>
<li><a
href="d281312a94"><code>Use
built-in mkdir instead of mkdirp</code></a></li>
<li><a
href="0411fb11c4"><code>format
readme</code></a></li>
<li><a
href="d5f986a516"><code>limit
normalize-unicode cache size</code></a></li>
<li><a
href="e79814eaf3"><code>remove
dirCache from mkdir, unpack</code></a></li>
<li><a
href="d3cd4392d4"><code>remove
extraneous &#39;for example&#39;</code></a></li>
<li><a
href="02f883aa4d"><code>docs
on how to use onWriteEntry</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-30 14:53:29 -04:00
depfu[bot]
5cd49b057b
Update @playwright/test 1.55.0 → 1.55.1 (patch) (#19033)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​playwright/test (1.55.0 → 1.55.1) ·
[Repo](https://github.com/Microsoft/playwright)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/microsoft/playwright/releases/tag/v1.55.1">1.55.1</a></h4>

<blockquote><h3 dir="auto">Highlights</h3>
<p dir="auto"><a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/37479">#37479</a>
- [Bug]: Upgrade Chromium to 140.0.7339.186.<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/37147">#37147</a>
- [Regression]: Internal error: step id not found.<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/37146">#37146</a>
- [Regression]: HTML reporter displays a broken chip link when there are
no projects.<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/pull/37137">#37137</a>
- Revert "fix(a11y): track inert elements as hidden".</p>
<h2 dir="auto">Browser Versions</h2>
<ul dir="auto">
<li>Chromium 140.0.7339.186</li>
<li>Mozilla Firefox 141.0</li>
<li>WebKit 26.0</li>
</ul>
<p dir="auto">This version was also tested against the following stable
channels:</p>
<ul dir="auto">
<li>Google Chrome 139</li>
<li>Microsoft Edge 139</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="f992162f04...ae51df7a35">See
the full diff on Github</a>. The new version differs by 7 commits:</p>
<ul>
<li><a
href="ae51df7a35"><code>chore:
mark v1.55.1 (#37530)</code></a></li>
<li><a
href="86dde294ce"><code>feat(chromium):
roll to r1193 (#37529)</code></a></li>
<li><a
href="86328bc9f4"><code>chore:
do not use -k option (#37532)</code></a></li>
<li><a
href="63799ba683"><code>cherry-pick(#37214):
docs: fix method names in release notes</code></a></li>
<li><a
href="21e29a42ab"><code>cherry-pick(#37153):
fix(html): don&#39;t display a chip with empty content with no
projects</code></a></li>
<li><a
href="ba62e6ab0d"><code>cherry-pick(#37149):
fix(test): attaching in boxed fixture</code></a></li>
<li><a
href="25bb073f26"><code>cherry-pick(#37137):
Revert &quot;fix(a11y): track inert elements as hidden
(#36947)&quot;</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-30 14:52:43 -04:00
govizlora
47d6a023e3
Ensure first class inside className in React is migrated (#19031)
### 1. Handling React className migration

The PR fixes an issue when migrating React components to tailwind v4
with the migration tool, that the first class after `className="` is
ignored.

For example, when migrating
```JSX
<div className="shadow"></div>
```
`shadow` will not be migrated to `shadow-sm` .

This is because in `is-safe-migration.ts`, it tests the line before
candidate with regex `/(?<!:?class)=['"]$/`. This basically skips the
migration for anything like `foo="shadow"`, with only exception for Vue
(eg. `class="shadow"`).

The PR changes the regex from

```regex
/(?<!:?class)=['"]$/
````
to
```regex
/(?<!:?class|className)=['"]$/
```
which essentially adds a new exception specifically for React's
`className="shadow"` case.

### 2. Removing redundant rules

Besides, I found that several other rules in
`CONDITIONAL_TEMPLATE_SYNTAX` being redundant since they are already
covered by the rule above, so I removed them. If we prefer the previous
explicit approach, I can revert it.

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->

Tests added for both the Vue and React classes to prevent false negative
cases.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-09-30 11:13:14 -04:00
Robin Malfait
9d00662e10
Show version mismatch when running upgrade tool (#19028)
This PR fixes an issue where sometimes people try to run the upgrade
tool, reset the changes and then try again.

If this happens, then the `package.json` and/or your lock file will
point to the old Tailwind CSS v3 version, but the actual installed
version will be v4.

This will also cause the upgrade tool to now upgrade from v4 to v4,
which is not what most people want if they were trying to upgrade from
v3 to v4. This in turn will cause some issues because now we won't try
to migrate the config file, or v3-specific classes that also exist in v4
but are only safe to upgrade from v3 to v4.

This PR uses `npm ls tailwindcss` to determine the actual installed
version. This command already errors if there is a mismatch between the
installed version and the version in `package.json` or the lock file.
This also happens to work in pnpm and bun projects (added integration
tests for these).

If for whatever reason we can't determine the expected version, we fall
back to the old behavior of just upgrading. In this scenario, the
changes introduced in
https://github.com/tailwindlabs/tailwindcss/pull/19026 will at least
give you a hint of what version was actually installed.

### Test plan

1. Tested it in a v3 project where I performed the following steps:
   1. Run the upgrade tool in full (`npx tailwindcss-upgrade`)
   2. Reset the changes (`git reset --hard && git clean -df`)
   1. Run the upgrade tool again

This resulted in the following output: <img width="1059" height="683"
alt="image"
src="https://github.com/user-attachments/assets/1d2ea2d1-b602-4631-958f-cc21eb8a633f"
/>


2. Added some integration tests to make sure this also works in pnpm,
bun and normal npm projects.

[ci-all]
2025-09-29 16:21:44 +02:00
Robin Malfait
b497e1eaf3
Add Upgrading from Tailwind CSS v… when running upgrade tool (#19026)
This PR adds a bit more information when running the upgrade tool to
know what version of Tailwind CSS you're upgrading from. This will help
users and maintainers when things go wrong.

Will have another PR up soon that errors when the Tailwind CSS version
in package.json and node_modules don't match.

### Test plan

Ran this one one of our older projects and saw the version logged
correctly.

<img width="1055" height="363" alt="image"
src="https://github.com/user-attachments/assets/5cbf4c52-ea0f-42c8-bd55-5bae2ed511de"
/>
2025-09-29 15:59:54 +02:00
depfu[bot]
f6bb72cf46
Update jiti 2.5.1 → 2.6.0 (minor) (#19029)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ jiti (2.5.1 → 2.6.0) · [Repo](https://github.com/unjs/jiti) ·
[Changelog](https://github.com/unjs/jiti/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/unjs/jiti/releases/tag/v2.6.0">2.6.0</a></h4>

<blockquote><p dir="auto"><a
href="https://bounce.depfu.com/github.com/unjs/jiti/compare/v2.5.1...v2.6.0">compare
changes</a></p>
<h3 dir="auto">🌟 What is new?</h3>
<p dir="auto">This release fixes minor issues, migrates to Rspack for
dist, and lazily imports the Babel transformer only when needed, which
should noticeably improve startup times.</p>
<ul dir="auto">
<li>Install size reduced from <code class="notranslate">2.03MB</code> to
<code class="notranslate">1.67MB</code>
</li>
<li>Loading times improved <code class="notranslate">150ms</code> =&gt;
<code class="notranslate">22ms</code> (full transform: <code
class="notranslate">180ms</code> =&gt; <code
class="notranslate">115ms</code>)</li>
</ul>
<h3 dir="auto">🔥 Performance</h3>
<ul dir="auto">
<li>Lazy load transformer (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/405">#405</a>)</li>
</ul>
<h3 dir="auto">🩹 Fixes</h3>
<ul dir="auto">
<li>
<strong>cjs-interop:</strong> Handle function default exports (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/396">#396</a>)</li>
<li>Always use native require/import for <code
class="notranslate">node:</code> specifiers (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/392">#392</a>)</li>
</ul>
<h3 dir="auto">📦 Build</h3>
<ul dir="auto">
<li>Migrate to rspack (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/404">#404</a>)</li>
<li>Updated bundled dependencies (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/compare/v2.5.1...v2.6.0#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">diff</a>)</li>
</ul>
<h3 dir="auto">✅ Tests</h3>
<ul dir="auto">
<li>Update deno and bun native test coverage (<a
href="df844f8">df844f8</a>)</li>
</ul>
<h3 dir="auto">❤️ Contributors</h3>
<ul dir="auto">
<li>Pooya Parsa (<a
href="https://bounce.depfu.com/github.com/pi0">@pi0</a>)</li>
<li>Volodymyr Kolesnykov (<a
href="https://bounce.depfu.com/github.com/sjinks">@sjinks</a>)</li>
<li>Jungwoo LEE (<a
href="https://bounce.depfu.com/github.com/jungwoo3490">@jungwoo3490</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="61fa80358b...82919cff8b">See
the full diff on Github</a>. The new version differs by 11 commits:</p>
<ul>
<li><a
href="82919cff8b"><code>chore(release):
v2.6.0</code></a></li>
<li><a
href="c48a5d5d55"><code>perf:
lazy load babel transform (#405)</code></a></li>
<li><a
href="c3f6ff4fcb"><code>fix:
always use native for `node:` specifiers (#392)</code></a></li>
<li><a
href="7aa365b813"><code>fix(cjs-interop):
handle function default exports (#396)</code></a></li>
<li><a
href="f9d67d2d89"><code>build:
migrate to rspack (#404)</code></a></li>
<li><a
href="64044278c6"><code>test:
new bench script</code></a></li>
<li><a
href="df844f8868"><code>test:
update deno and bun native test ignores</code></a></li>
<li><a
href="5123334eb2"><code>chore:
update deps</code></a></li>
<li><a
href="cdb86cbeeb"><code>chore(deps):
update all non-major dependencies (#393)</code></a></li>
<li><a
href="e615852fb0"><code>chore(deps):
update actions/checkout action to v5 (#394)</code></a></li>
<li><a
href="d2d9aebb8c"><code>chore(deps):
update actions/setup-node action to v5 (#399)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-29 09:51:45 -04:00
depfu[bot]
9feefacf81
Update bun 1.2.20 → 1.2.22 (patch) (#19007)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ bun (1.2.20 → 1.2.22) · [Repo](https://github.com/oven-sh/bun)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-29 09:16:06 +00:00
depfu[bot]
0d39788ec9
Update @tailwindcss/typography 0.5.16 → 0.5.19 (minor) (#19014)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​tailwindcss/typography (0.5.16 → 0.5.19) ·
[Repo](https://github.com/tailwindlabs/tailwindcss-typography) ·
[Changelog](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/tailwindlabs/tailwindcss-typography/releases/tag/v0.5.19">0.5.19</a></h4>

<blockquote><h3 dir="auto">Fixed</h3>
<ul dir="auto">
<li>Fixed broken color styles (<a
href="https://bounce.depfu.com/github.com/tailwindlabs/tailwindcss-typography/pull/405">#405</a>)</li>
</ul></blockquote>
<h4><a
href="https://github.com/tailwindlabs/tailwindcss-typography/releases/tag/v0.5.18">0.5.18</a></h4>

<blockquote><h3 dir="auto">Fixed</h3>
<ul dir="auto">
<li>Fixed undefined variable error (<a
href="https://bounce.depfu.com/github.com/tailwindlabs/tailwindcss-typography/pull/403">#403</a>)</li>
</ul></blockquote>
<h4><a
href="https://github.com/tailwindlabs/tailwindcss-typography/releases/tag/v0.5.17">0.5.17</a></h4>

<blockquote><h3 dir="auto">Added</h3>
<ul dir="auto">
<li>Add modifiers for description list elements (<a
href="https://bounce.depfu.com/github.com/tailwindlabs/tailwindcss-typography/pull/357">#357</a>)</li>
<li>Add <code class="notranslate">prose-picture</code> modifier (<a
href="https://bounce.depfu.com/github.com/tailwindlabs/tailwindcss-typography/pull/367">#367</a>)</li>
</ul>
<h3 dir="auto">Fixed</h3>
<ul dir="auto">
<li>Include unit in <code class="notranslate">hr</code> border-width
value (<a
href="https://bounce.depfu.com/github.com/tailwindlabs/tailwindcss-typography/pull/379">#379</a>)</li>
<li>Ensure <code class="notranslate">&lt;kbd&gt;</code> styles work with
Tailwind CSS v4 (<a
href="https://bounce.depfu.com/github.com/tailwindlabs/tailwindcss-typography/pull/387">#387</a>)</li>
</ul>
<h3 dir="auto">Changed</h3>
<ul dir="auto">
<li>Remove lodash dependencies (<a
href="https://bounce.depfu.com/github.com/tailwindlabs/tailwindcss-typography/pull/402">#402</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="39d20e1949...e002ab89ad">See
the full diff on Github</a>. The new version differs by 22 commits:</p>
<ul>
<li><a
href="e002ab89ad"><code>0.5.19</code></a></li>
<li><a
href="bbb1c21099"><code>Fix
bad RGB syntax (#405)</code></a></li>
<li><a
href="b316f958af"><code>0.5.18</code></a></li>
<li><a
href="ed952066e6"><code>Fix
variable declaration in opacity function (#403)</code></a></li>
<li><a
href="7efcb4a499"><code>0.5.17</code></a></li>
<li><a
href="e0ec248baf"><code>chore(ci):
update actions for release insiders</code></a></li>
<li><a
href="511afcb0bd"><code>Add
modifiers for description list elements (#357)</code></a></li>
<li><a
href="042a531528"><code>Add
`prose-picture` modifiers (#367)</code></a></li>
<li><a
href="f822222ae6"><code>Fix
`kbd` shadow colors not being calculated on oklch colors
(#387)</code></a></li>
<li><a
href="ecb7e87a52"><code>Add
Tailwind v4 custom color theme example to README (#396)</code></a></li>
<li><a
href="ecb7d5c435"><code>Remove
lodash dependencies (#402)</code></a></li>
<li><a
href="b7cdf1e1ec"><code>Clarify
&#39;not-prose&#39; usage when using prefixes (#399)</code></a></li>
<li><a
href="25051fbfd7"><code>Fix
syntax highlighting in readme</code></a></li>
<li><a
href="d3c1fbdc60"><code>Include
v3 installation instructions alongside v4 (#388)</code></a></li>
<li><a
href="632970e3ce"><code>Readme:
Remove unused `{theme}` (#385)</code></a></li>
<li><a
href="c92dc1c120"><code>Fix
typo in comments (#378)</code></a></li>
<li><a
href="3e75cb0480"><code>Change
the borderTopWidth value for hr from integer to a pixel value
(#379)</code></a></li>
<li><a
href="1a6972e690"><code>Rectify
variant order Closes #376</code></a></li>
<li><a
href="0ab25dc0ff"><code>Fix
plugin import path in README.md (#382)</code></a></li>
<li><a
href="fb252ece73"><code>Fix
syntax errors in Readme.md (#381)</code></a></li>
<li><a
href="43a4c2c2fa"><code>Update
README for Tailwind CSS v4 (#380)</code></a></li>
<li><a
href="d1e6421d4c"><code>Update
README.md</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-26 16:10:51 -04:00
depfu[bot]
20f830f687
Update prettier-plugin-organize-imports 4.2.0 → 4.3.0 (minor) (#19013)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ prettier-plugin-organize-imports (4.2.0 → 4.3.0) ·
[Repo](https://github.com/simonhaenisch/prettier-plugin-organize-imports)
·
[Changelog](https://github.com/simonhaenisch/prettier-plugin-organize-imports/blob/master/changelog.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/simonhaenisch/prettier-plugin-organize-imports/releases/tag/v4.3.0">4.3.0</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>feat: allow configuration of <code
class="notranslate">organizeImportsTypeOrder</code>(<a
href="https://bounce.depfu.com/github.com/simonhaenisch/prettier-plugin-organize-imports/pull/152">#152</a>)
- thanks <a
href="https://bounce.depfu.com/github.com/goege64">@goege64</a> for your
first contribution 🎉</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/simonhaenisch/prettier-plugin-organize-imports/compare/v4.2.0...v4.3.0"><tt>v4.2.0...v4.3.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="93df5019c4...f354c0ef86">See
the full diff on Github</a>. The new version differs by 4 commits:</p>
<ul>
<li><a
href="f354c0ef86"><code>4.3.0</code></a></li>
<li><a
href="98d053aa16"><code>chore:
update dev dependencies</code></a></li>
<li><a
href="bc4d8facb0"><code>feat:
allow configuration of `organizeImportsTypeOrder` (#152)</code></a></li>
<li><a
href="ed9c19a5b1"><code>docs:
update changelog</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-26 16:10:41 -04:00
depfu[bot]
cc8fefc154 Update magic-string to version 0.30.19 2025-09-26 19:12:26 +00:00
depfu[bot]
1f844244cb Update all of nextjs to version 15.5.2 2025-09-26 18:57:13 +00:00
depfu[bot]
210575a6a5
Update dedent 1.6.0 → 1.7.0 (minor) (#19010)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ dedent (1.6.0 → 1.7.0) · [Repo](https://github.com/dmnd/dedent)
· [Changelog](https://github.com/dmnd/dedent/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/dmnd/dedent/releases/tag/v1.7.0">1.7.0</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>docs: cleaned up README.md badges by <a
href="https://bounce.depfu.com/github.com/JoshuaKGoldberg">@JoshuaKGoldberg</a>
in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/100">#100</a>
</li>
<li>feat: add alignValues option by <a
href="https://bounce.depfu.com/github.com/PaperStrike">@PaperStrike</a>
in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/102">#102</a>
</li>
<li>1.7.0 by <a
href="https://bounce.depfu.com/github.com/JoshuaKGoldberg">@JoshuaKGoldberg</a>
in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/103">#103</a>
</li>
</ul>
<h2 dir="auto">New Contributors</h2>
<ul dir="auto">
<li>
<a
href="https://bounce.depfu.com/github.com/PaperStrike">@PaperStrike</a>
made their first contribution in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/102">#102</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/compare/v1.6.0...v1.7.0"><tt>v1.6.0...v1.7.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="ab2ce25762...dd15cf5836">See
the full diff on Github</a>. The new version differs by 3 commits:</p>
<ul>
<li><a
href="dd15cf5836"><code>1.7.0
(#103)</code></a></li>
<li><a
href="304d0fc795"><code>feat:
add alignValues option (#102)</code></a></li>
<li><a
href="aab442c691"><code>docs:
cleaned up README.md badges (#100)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-26 14:42:26 -04:00
depfu[bot]
3a4eb389b6
Update eslint 9.33.0 → 9.35.0 (minor) (#19009)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.33.0 → 9.35.0) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.35.0">9.35.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="42761fa7c8"><code
class="notranslate">42761fa</code></a> feat: implement suggestions for
no-empty-function (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20057">#20057</a>)
(jaymarvelz)</li>
<li>
<a
href="102f44442a"><code
class="notranslate">102f444</code></a> feat: implement suggestions for
no-empty-static-block (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20056">#20056</a>)
(jaymarvelz)</li>
<li>
<a
href="e51ffff737"><code
class="notranslate">e51ffff</code></a> feat: add <code
class="notranslate">preserve-caught-error</code> rule (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19913">#19913</a>)
(Amnish Singh Arora)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="10e7ae23e3"><code
class="notranslate">10e7ae2</code></a> fix: update uncloneable options
error message (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20059">#20059</a>)
(soda-sorcery)</li>
<li>
<a
href="bfa46013e7"><code
class="notranslate">bfa4601</code></a> fix: ignore empty switch
statements with comments in no-empty rule (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20045">#20045</a>)
(jaymarvelz)</li>
<li>
<a
href="dfd11deb24"><code
class="notranslate">dfd11de</code></a> fix: add <code
class="notranslate">before</code> and <code
class="notranslate">after</code> to test case types (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20049">#20049</a>)
(Francesco Trotta)</li>
<li>
<a
href="dabbe95c39"><code
class="notranslate">dabbe95</code></a> fix: correct types for <code
class="notranslate">no-restricted-imports</code> rule (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20034">#20034</a>)
(Milos Djermanovic)</li>
<li>
<a
href="ea789c7dd2"><code
class="notranslate">ea789c7</code></a> fix: no-loss-of-precision false
positive with uppercase exponent (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20032">#20032</a>)
(sethamus)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="d265515642"><code
class="notranslate">d265515</code></a> docs: improve phrasing - "if" →
"even if" from getting-started section (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20074">#20074</a>)
(jjangga0214)</li>
<li>
<a
href="a355a0e5b2"><code
class="notranslate">a355a0e</code></a> docs: invert comparison logic for
example in <code class="notranslate">no-var</code> doc page (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20064">#20064</a>)
(OTonGitHub)</li>
<li>
<a
href="5082fc206d"><code
class="notranslate">5082fc2</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="99cfd7e056"><code
class="notranslate">99cfd7e</code></a> docs: add missing "the" in rule
deprecation docs (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20050">#20050</a>)
(Josh Goldberg ✨)</li>
<li>
<a
href="6ad8973e5d"><code
class="notranslate">6ad8973</code></a> docs: update <code
class="notranslate">--no-ignore</code> and <code
class="notranslate">--ignore-pattern</code> documentation (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20036">#20036</a>)
(Francesco Trotta)</li>
<li>
<a
href="8033b19529"><code
class="notranslate">8033b19</code></a> docs: add documentation for <code
class="notranslate">--no-config-lookup</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20033">#20033</a>)
(Francesco Trotta)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="da87f2fe79"><code
class="notranslate">da87f2f</code></a> chore: upgrade @eslint/js@9.35.0
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20077">#20077</a>)
(Milos Djermanovic)</li>
<li>
<a
href="af2a0870fd"><code
class="notranslate">af2a087</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="70557649e3"><code
class="notranslate">7055764</code></a> test: remove <code
class="notranslate">tests/lib/eslint/eslint.config.js</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20065">#20065</a>)
(Milos Djermanovic)</li>
<li>
<a
href="84ffb9680b"><code
class="notranslate">84ffb96</code></a> chore: update <code
class="notranslate">@eslint-community/eslint-utils</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20069">#20069</a>)
(Francesco Trotta)</li>
<li>
<a
href="d5ef939715"><code
class="notranslate">d5ef939</code></a> refactor: remove deprecated <code
class="notranslate">context.parserOptions</code> usage across rules (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20060">#20060</a>)
(sethamus)</li>
<li>
<a
href="1b3881d7e8"><code
class="notranslate">1b3881d</code></a> chore: remove redundant word (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20058">#20058</a>)
(pxwanglu)</li>
</ul></blockquote>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.34.0">9.34.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="0bb777a82b"><code
class="notranslate">0bb777a</code></a> feat: multithread linting (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19794">#19794</a>)
(Francesco Trotta)</li>
<li>
<a
href="43a5f9e36f"><code
class="notranslate">43a5f9e</code></a> feat: add eslint-plugin-regexp to
eslint-config-eslint base config (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19951">#19951</a>)
(Pixel998)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="9b8990397b"><code
class="notranslate">9b89903</code></a> fix: default value of
accessor-pairs option in rule.d.ts file (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20024">#20024</a>)
(Tanuj Kanti)</li>
<li>
<a
href="6c074206ae"><code
class="notranslate">6c07420</code></a> fix: fix spurious failure in
neostandard integration test (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20023">#20023</a>)
(Kirk Waiblinger)</li>
<li>
<a
href="676f4acaae"><code
class="notranslate">676f4ac</code></a> fix: allow scientific notation
with trailing zeros matching exponent (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20002">#20002</a>)
(Sweta Tanwar)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="0b4a590333"><code
class="notranslate">0b4a590</code></a> docs: make rulesdir deprecation
clearer (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20018">#20018</a>)
(Domenico Gemoli)</li>
<li>
<a
href="327c67256f"><code
class="notranslate">327c672</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="bf2622991f"><code
class="notranslate">bf26229</code></a> docs: Fix typo in
core-concepts/index.md (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20009">#20009</a>)
(Tobias Hernstig)</li>
<li>
<a
href="2309327554"><code
class="notranslate">2309327</code></a> docs: fix typo in the
"Configuring Rules" section (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20001">#20001</a>)
(ghazi-git)</li>
<li>
<a
href="2b87e21321"><code
class="notranslate">2b87e21</code></a> docs: [no-else-return] clarify
sample code. (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19991">#19991</a>)
(Yuki Takada (Yukinosuke Takada))</li>
<li>
<a
href="c36570c665"><code
class="notranslate">c36570c</code></a> docs: Update README (GitHub
Actions Bot)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="f19ad9493e"><code
class="notranslate">f19ad94</code></a> chore: upgrade to <code
class="notranslate">@eslint/js@9.34.0</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20030">#20030</a>)
(Francesco Trotta)</li>
<li>
<a
href="b48fa20034"><code
class="notranslate">b48fa20</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="4bce8a2502"><code
class="notranslate">4bce8a2</code></a> chore: package.json update for
eslint-config-eslint release (Jenkins)</li>
<li>
<a
href="0c9999c2a6"><code
class="notranslate">0c9999c</code></a> refactor: prefer default options
in <code class="notranslate">grouped-accessor-pairs</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20028">#20028</a>)
(루밀LuMir)</li>
<li>
<a
href="d503f19813"><code
class="notranslate">d503f19</code></a> ci: fix <code
class="notranslate">stale.yml</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20010">#20010</a>)
(루밀LuMir)</li>
<li>
<a
href="e2dc67d8b0"><code
class="notranslate">e2dc67d</code></a> ci: centralize <code
class="notranslate">stale.yml</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19994">#19994</a>)
(루밀LuMir)</li>
<li>
<a
href="7093cb8f59"><code
class="notranslate">7093cb8</code></a> ci: bump actions/checkout from 4
to 5 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/20005">#20005</a>)
(dependabot[bot])</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="a90d7c4fe5...8401101d1e">See
the full diff on Github</a>. The new version differs by 42 commits:</p>
<ul>
<li><a
href="8401101d1e"><code>9.35.0</code></a></li>
<li><a
href="b80f0254f3"><code>Build:
changelog update for 9.35.0</code></a></li>
<li><a
href="da87f2fe79"><code>chore:
upgrade @eslint/js@9.35.0 (#20077)</code></a></li>
<li><a
href="af2a0870fd"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="d265515642"><code>docs:
improve phrasing - &quot;if&quot; → &quot;even if&quot; from
getting-started section (#20074)</code></a></li>
<li><a
href="70557649e3"><code>test:
remove `tests/lib/eslint/eslint.config.js` (#20065)</code></a></li>
<li><a
href="10e7ae23e3"><code>fix:
update uncloneable options error message (#20059)</code></a></li>
<li><a
href="42761fa7c8"><code>feat:
implement suggestions for no-empty-function (#20057)</code></a></li>
<li><a
href="102f44442a"><code>feat:
implement suggestions for no-empty-static-block (#20056)</code></a></li>
<li><a
href="84ffb9680b"><code>chore:
update `@eslint-community/eslint-utils` (#20069)</code></a></li>
<li><a
href="a355a0e5b2"><code>docs:
invert comparison logic for example in `no-var` doc page
(#20064)</code></a></li>
<li><a
href="e51ffff737"><code>feat:
add `preserve-caught-error` rule (#19913)</code></a></li>
<li><a
href="5082fc206d"><code>docs:
Update README</code></a></li>
<li><a
href="d5ef939715"><code>refactor:
remove deprecated `context.parserOptions` usage across rules
(#20060)</code></a></li>
<li><a
href="1b3881d7e8"><code>chore:
remove redundant word (#20058)</code></a></li>
<li><a
href="99cfd7e056"><code>docs:
add missing &quot;the&quot; in rule deprecation docs
(#20050)</code></a></li>
<li><a
href="bfa46013e7"><code>fix:
ignore empty switch statements with comments in no-empty rule
(#20045)</code></a></li>
<li><a
href="dfd11deb24"><code>fix:
add `before` and `after` to test case types (#20049)</code></a></li>
<li><a
href="6ad8973e5d"><code>docs:
update `--no-ignore` and `--ignore-pattern` documentation
(#20036)</code></a></li>
<li><a
href="dabbe95c39"><code>fix:
correct types for `no-restricted-imports` rule (#20034)</code></a></li>
<li><a
href="8033b19529"><code>docs:
add documentation for `--no-config-lookup` (#20033)</code></a></li>
<li><a
href="ea789c7dd2"><code>fix:
no-loss-of-precision false positive with uppercase exponent
(#20032)</code></a></li>
<li><a
href="b8875f67a7"><code>9.34.0</code></a></li>
<li><a
href="2e455fb433"><code>Build:
changelog update for 9.34.0</code></a></li>
<li><a
href="f19ad9493e"><code>chore:
upgrade to `@eslint/js@9.34.0` (#20030)</code></a></li>
<li><a
href="b48fa20034"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="4bce8a2502"><code>chore:
package.json update for eslint-config-eslint release</code></a></li>
<li><a
href="0c9999c2a6"><code>refactor:
prefer default options in `grouped-accessor-pairs`
(#20028)</code></a></li>
<li><a
href="0b4a590333"><code>docs:
make rulesdir deprecation clearer (#20018)</code></a></li>
<li><a
href="9b8990397b"><code>fix:
default value of accessor-pairs option in rule.d.ts file
(#20024)</code></a></li>
<li><a
href="6c074206ae"><code>fix:
fix spurious failure in neostandard integration test
(#20023)</code></a></li>
<li><a
href="676f4acaae"><code>fix:
allow scientific notation with trailing zeros matching exponent
(#20002)</code></a></li>
<li><a
href="327c67256f"><code>docs:
Update README</code></a></li>
<li><a
href="d503f19813"><code>ci:
fix `stale.yml` (#20010)</code></a></li>
<li><a
href="0bb777a82b"><code>feat:
multithread linting (#19794)</code></a></li>
<li><a
href="bf2622991f"><code>docs:
Fix typo in core-concepts/index.md (#20009)</code></a></li>
<li><a
href="43a5f9e36f"><code>feat:
add eslint-plugin-regexp to eslint-config-eslint base config
(#19951)</code></a></li>
<li><a
href="e2dc67d8b0"><code>ci:
centralize `stale.yml` (#19994)</code></a></li>
<li><a
href="7093cb8f59"><code>ci:
bump actions/checkout from 4 to 5 (#20005)</code></a></li>
<li><a
href="2309327554"><code>docs:
fix typo in the &quot;Configuring Rules&quot; section
(#20001)</code></a></li>
<li><a
href="2b87e21321"><code>docs:
[no-else-return] clarify sample code. (#19991)</code></a></li>
<li><a
href="c36570c665"><code>docs:
Update README</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-26 14:39:44 -04:00
depfu[bot]
de726339e7
Update @types/semver 7.7.0 → 7.7.1 (patch) (#19005)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/semver (7.7.0 → 7.7.1) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-26 14:39:32 -04:00
depfu[bot]
d593a2101a
Update @types/react 19.1.9 → 19.1.13 (patch) (#19003)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react (19.1.9 → 19.1.13) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-26 14:39:22 -04:00
depfu[bot]
5aa2bf1d90
Update @vitejs/plugin-react 5.0.0 → 5.0.3 (patch) (#19006) 2025-09-26 18:43:05 +02:00
depfu[bot]
a800585478
Update @types/react-dom 19.1.7 → 19.1.9 (patch) (#19004)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react-dom (19.1.7 → 19.1.9) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-26 17:47:31 +02:00
depfu[bot]
d6464bda09
Update @types/bun 1.2.20 → 1.2.22 (patch) (#19002)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/bun (1.2.20 → 1.2.22) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

---------

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-09-26 15:42:15 +00:00
Robin Malfait
85575a41f4
Add Maud templating support (#18988)
This PR adds support for Maud templates in Rust.

We already had some pre-processing for Rust but for Leptos `class:`
syntax. This PR now added a dedicated Rust pre-processor that handles
Leptos and Maud syntax.

We only start pre-processing Maud templates if the Rust file includes
the `html!` macro.

## Test plan

Looking at the extractor, you can see that we now do extract the proper
classes in Maud templates:

<img width="1076" height="1856" alt="image"
src="https://github.com/user-attachments/assets/e649e1de-289e-466f-8fab-44a938a47dd5"
/>


Fixes: #18984
2025-09-24 11:33:01 +00:00
Jordan Pittman
c6e0a55d36
Ensure files with only @theme produce no output when built (#18979)
Closes #18978
2025-09-22 11:59:02 -04:00
Robin Malfait
9a5bae2038
Revert "Update all pnpm dependencies (2025-09-20) (#18971)"
This reverts commit 42eadfd1ee.
2025-09-22 11:34:40 +02:00
depfu[bot]
42eadfd1ee
Update all pnpm dependencies (2025-09-20) (#18971)
This is your weekly update of **all** pnpm dependencies. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?

✳️ eslint (9.33.0 → 9.35.0, minor) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md) ·
[Release](https://github.com/eslint/eslint/releases/tag/v9.35.0) ·
[Diff](a90d7c4fe5...8401101d1e)

✳️ eslint-config-next (15.4.7 → 15.5.2, minor)

✳️ magic-string (0.30.18 → 0.30.19, minor) ·
[Repo](https://github.com/rich-harris/magic-string) ·
[Changelog](https://github.com/Rich-Harris/magic-string/blob/master/CHANGELOG.md)
·
[Diff](0005025c18...bdef7d5ab5)

✳️ @types/bun (1.2.20 → 1.2.21, patch) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)

✳️ @types/react (19.1.9 → 19.1.13, patch) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)

✳️ @types/react-dom (19.1.7 → 19.1.9, patch) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)

✳️ @types/semver (7.7.0 → 7.7.1, patch) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)

✳️ @vitejs/plugin-react (5.0.0 → 5.0.2, patch) ·
[Repo](https://github.com/vitejs/vite-plugin-react) ·
[Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)

✳️ bun (1.2.20 → 1.2.21, patch) · [Repo](https://github.com/oven-sh/bun)

✳️ turbo (2.5.4 → 2.5.6, patch) ·
[Repo](https://github.com/turborepo/turbo)




---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will only send you the next scheduled PR once
you merge or close this one.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu refresh</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-22 11:11:39 +02:00
Philipp Spiess
6c30d5ea3a
Bump napi toolchain to 3.3 (#18947)
This PR bumps all napi related dependencies to their latest version.
Napi 3 has by now officially been released but since we've been using
the preview already, there aren't many changes.

One thing that ChatGPT found is that `--no-const-enum` is the default
behavior now (see default value [in this
table](https://napi.rs/docs/cli/build#options))

## Test plan

- `pnpm install && pnpm build`
- `cd crates/node/npm/wasm32-wasi`
- `pnpm install --ignore-workspace` (This is necessary for some reason
or the bundled dependencies won't work, I have no clue why it's not
necessary on CI)
- `pnpm pack`
- Install dependency in a new npm package with a simple config like
this:
    ```js
    import { Scanner } from "@tailwindcss/oxide-wasm32-wasi";

    let scanner = new Scanner({
      sources: [
        {
base:
"/Users/philipp/dev/tailwindcss/packages/@tailwindcss-postcss/src/fixtures/example-project",
          pattern: "**/*",
          negated: false,
        },
      ],
    });

    console.log(scanner.scan());
    ```
- <img width="904" height="494" alt="CleanShot 2025-09-19 at 14 53
52@2x"
src="https://github.com/user-attachments/assets/93e32c19-6db4-4d00-9fdb-a6fde22fc69c"
/>


I also tested the CI build to make sure the `bundledDependencies` are
properly added.
2025-09-19 17:08:41 +02:00
Jordan Pittman
8d56381012
Detect classes in markdown inline directives (#18967)
Fixes #18071
2025-09-18 16:53:44 -04:00
Jordan Pittman
d0f7f82787
Add plugin option documentation to the postcss plugin readme (#18940)
Closes https://github.com/tailwindlabs/tailwindcss.com/issues/2061

In lieu of finding a place to put this on the docs we can at least
document this in the readme for the plugin.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-09-18 13:00:35 -04:00
Jordan Pittman
5b8136e838
Re-throw errors from PostCSS nodes (#18373)
Fixes #18370

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-09-18 12:03:19 -04:00
Robin Malfait
c2aab49c77
Bump Prettier (#18960)
This PR bumps prettier and solves one of our `- *` formatting issues.
Not all, but a few!
2025-09-18 11:17:29 +02:00
Blake V.
5a94f81e7e
Use default export condition for @tailwindcss/vite (#18948)
## Summary

In `@tailwindcss/vite` 's `package.json`, change the `exports` key from
`include` to `default` since there is no `require` case.

Ran into an issue using the `tsx` package to run a script that has a
sub-dependency that imports from `@tailwindcss/vite`, where `tsx`
converts things to cjs to run, and since there is no `require` case for
this package, it can't find the file. Changing to `default` covers the
cases for both `import` and `require`.

## Test plan

No testing needed. Functionality is the same.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-09-16 15:04:13 -04:00
Robin Malfait
d0a1bd655b
Show Lightning CSS warnings when optimizing/minifying in production (#18918)
This PR improves the DX by showing all the Lightning CSS warnings when
using a "production" build (or using `--optimize` or `--minify` flags
when using the CLI).

Right now Tailwind CSS itself doesn't care about the exact syntax you
are using in the CSS as long as it looks valid. We do this because
otherwise we would have to parse a lot more CSS syntax and validate it
even though it would be valid CSS in 99.99% of the cases.

Even worse, if you want to use newer CSS syntax that Tailwind CSS
doesn't validate yet, then you would get warnings for valid CSS.

Another reason why we don't do this is because the browser already does
a great job at ignoring invalid CSS.

So the linked issue #15872 would still silently fail in development
mode. In this case, everything would work, except the shadow with the
invalid syntax.

But in production mode, you would now get a proper warning from
Lightning CSS, because they try to optimize the CSS and remove invalid
CSS.

One potential issue here is that we run Lightning CSS on the generated
CSS, not on the input CSS. So the current output shows the warnings in
the output CSS not the input CSS. Any thoughts if we would just skip the
line numbers?

## Test plan

1. Everything works as before
2. In production mode, you would get warnings printed to the terminal.
This is done in `@tailwindcss/node` so the CLI/Vite/PostCSS plugins
would all get the same behavior.

Screenshots:

If you have a single issue:
<img width="977" height="441" alt="image"
src="https://github.com/user-attachments/assets/7b061ee9-b74f-4b40-aa05-cff67a21dfcc"
/>


If you have multiple issues:
<img width="2170" height="711" alt="image"
src="https://github.com/user-attachments/assets/a5bc9b0a-964b-465f-80f3-d30dd467e69c"
/>


Fixes: #15872
2025-09-12 12:27:23 +02:00
Robin Malfait
65bad11380
Do not migrate variant = 'outline' during upgrades (#18922)
This PR improves the upgrade tool for shadcn/ui projects where the
`variant = "outline"` is incorrectly migrated to `variant =
"outline-solid"`.

This PR also handles a few more cases:

```ts
// As default argument
function Button({ variant = "outline", ...props }: ButtonProps) { }

// With different kinds of quotes (single, double, backticks)
function Button({ variant = 'outline', ...props }: ButtonProps) { }

// Regardless of whitespace
function Button({ variant="outline", ...props }: ButtonProps) { }

// In JSX
<Button variant="outline" />

// With different quotes and using JavaScript expressions
<Button variant={'outline'} />

// As an object property
buttonVariants({ variant: "outline" })
```
2025-09-12 09:20:18 +00:00
Philipp Spiess
d1fd645beb
Proposal: Allow overwriting static utilities that have a namespace (#18056)
This PR attempts to move static utilities that are overwriteable by a
theme value to be a fallback rather than a conflicting implementation.
The idea is to allow a theme value to take presedence over that static
utility _and cause it not to generate_.

For example, when overwriting the `--radius-full` variant, it should
ensure that the default `rounded-full` no longer emits the
`calc(infinity * 1px)` declaration:

```ts
expect(
  await compileCss(
    css`
      @theme {
        --radius-full: 99999px;
      }
      @tailwind utilities;
    `,
    ['rounded-full'],
  ),
).toMatchInlineSnapshot(`
  ":root, :host {
    --radius-full: 99999px;
  }

  .rounded-full {
    border-radius: var(--radius-full);
  }"
`)
```

This allows anyone who wants `--radius-full` to be a CSS variable to
simply define it in their theme:

```css
@theme {
  /* Make `--radius-full` a CSS variable without the utility generating two CSS classes */
  --radius-full: calc(infinity * 1px);
}
```

The idea is to extend this pattern across all functional utilities that
also have static utilities that can collide with the namespace. This
gives users more control over what they want as CSS variables when the
defaults don't work for them, allowing them to resolve #16639 and #15115
in user space.

You may now find yourself thinking "but Philipp, why would someone want
to be able to overwrite `--animate-none`. `none` surely always will mean
no animation" and I would agree [but it's already possible right now
anyways so this is not a new behavior! This PR just cleans up the
generated output.](https://play.tailwindcss.com/StnQqm4V2e)

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-09-11 12:21:50 +02:00
Robin Malfait
340b59dcde
Do not generate grid-column when configuring grid-column-start or grid-column-end (#18907)
This PR fixes an issue where configuring a custom `--grid-column-start`
or
`--grid-column-end` also generated a `grid-column` utility due to the
overlapping namespace.

```css
@theme {
  --grid-column-start-custom: custom-start;
  --grid-column-end-custom: custom-end;
}
```

Would then generate:
```css
.col-end-custom {
  grid-column: var(--grid-column-end-custom);
}
.col-start-custom {
  grid-column: var(--grid-column-start-custom);
}
.col-start-custom {
  grid-column-start: var(--grid-column-start-custom);
}
.col-end-custom {
  grid-column-end: var(--grid-column-end-custom);
}
```

Instead of the expected:
```css
.col-start-custom {
  grid-column-start: var(--grid-column-start-custom);
}
.col-end-custom {
  grid-column-end: var(--grid-column-end-custom);
}
```

Fixes: #18906
2025-09-10 15:57:29 +02:00
Philipp Spiess
ee1c7a69dc
Fix CLI watcher cleanup race (#18905)
This PR supersets #18559 and fixes the same issue reported by @Gazler.

Upon testing, we noticed that it's possible that two parallel
invocations of file system change events could cause some cleanup
functions to get swallowed.

This happens because we only remember one global cleanup function but it
is possible timing wise that two calls to `createWatcher()` are created
before the old watchers are cleaned and thus only one of the new cleanup
functions get retained.

To fix this, this PR changes `cleanupWatchers` to an array and ensures
that all functions are retained.

In some local testing, I was able to trigger this, based on the
reproduction by @Gazler in
https://github.com/tailwindlabs/tailwindcss/pull/18559, to often call a
cleanup with more than one cleanup function in the array.

I'm going to paste the amazing reproduction from #18559 here as well:


# Requirements

We need a way to stress the CPU to slow down tailwind compilation, for
example stress-ng.

```
stress-ng --cpu 16 --timeout 10
```

It can be install with apt, homebrew or similar.

# Installation

There is a one-liner at the bottom to perform the required setup and run
the tailwindcli.

Create a new directory:

```shell
mkdir twtest && cd twtest
```

Create a package.json with the correct deps.

```shell
cat << 'EOF' > package.json
{
  "dependencies": {
    "@tailwindcss/cli": "^4.1.11",
    "daisyui": "^5.0.46",
    "tailwindcss": "^4.1.11"
  }
}
EOF
```

Create the input css:

```shell
mkdir src
cat << 'EOF' > src/.input.css
@import "tailwindcss" source(none);
@plugin "daisyui";
@source "../core_components.ex";
@source "../home.html.heex";
@source "./input.css";

EOF
```

Install tailwind, daisyui, and some HTML to make tailwind do some work:

```
npm install
wget https://raw.githubusercontent.com/phoenixframework/phoenix/refs/heads/main/installer/templates/phx_web/components/core_components.ex
wget https://github.com/phoenixframework/phoenix/blob/main/installer/templates/phx_web/controllers/page_html/home.html.heex
```

# Usage

This is easiest with 3 terminal windows:

Start a tailwindcli watcher in one terminal:

```shell
npx @tailwindcss/cli -i src/input.css -o src/output.css --watch
```

Start a stress test in another:

```shell
stress-ng --cpu 16 --timeout 30
```

Force repeated compilation in another:

```shell
for i in $(seq 1 50); do touch src/input.css; sleep 0.1; done
```

# Result

Once the stress test has completed, you can run:

```shell
touch src/input.css
```

You should see that there is repeated output, and the duration is in the
multiple seconds.

If this setup doesn't cause the issue, you can also add the `-p` flag
which causes the
CSS to be printed, slowing things down further:

```shell
npx @tailwindcss/cli -i src/input.css -p --watch
```

## One-liner

```shell
mkdir twtest && cd twtest
cat << 'EOF' > package.json
{
  "dependencies": {
    "@tailwindcss/cli": "^4.1.11",
    "daisyui": "^5.0.46",
    "tailwindcss": "^4.1.11"
  }
}
EOF

mkdir src
cat << 'EOF' > src/input.css
@import "tailwindcss" source(none);
@plugin "daisyui";
@source "../core_components.ex";
@source "../home.html.heex";
@source "./input.css";

EOF

npm install
wget https://raw.githubusercontent.com/phoenixframework/phoenix/refs/heads/main/installer/templates/phx_web/components/core_components.ex
wget https://github.com/phoenixframework/phoenix/blob/main/installer/templates/phx_web/controllers/page_html/home.html.heex
npx @tailwindcss/cli -i src/input.css -o src/output.css --watch
```

## Test plan

- Not able to reproduce this with a local build of the CLI after the
patch is applied but was able to reproduce it again once the patch was
reverted.

Co-authored-by: Gary Rennie <gazler@gmail.com>
2025-09-09 17:14:09 +02:00
Robin Malfait
b7c7e48c5d
Add @container-size utility (#18901)
This PR adds a new `@container-size` utility instead of
`@container-[size]`.

The main reason we didn't do this before is because we only have
container width related container queries, and not block based ones so
we never needed `size` and `inline-size` was enough.

However, `@container-size` is still useful if you are using container
query related units such as `cqb` which are using the block size of the
container not the inline size.

I also added a little helper such that `@container-size` is only
available in `insiders` and `4.2.0` (and later) so `4.1.x` releases
won't have this utility yet. This will require some CHANGELOG changes
such that we don't include this when releasing the next minor release.
2025-09-09 12:20:04 +00:00
Robin Malfait
2f1cbbfed2
Merge suggestions when using @utility (#18900)
This PR fixes a bug where custom `@utility` implementations with a name
that match an existing utility would override the existing suggestions
even though we generate both utilities.

With this, we want to make sure that both the custom and the built-in
utilities are suggested. We also want to make sure that we don't get
duplicate suggestions.

E.g.:

- `font-` would suggest:
  - 'font-black'
  - 'font-bold'
  - 'font-extrabold'
  - 'font-extralight'
  - 'font-light'
  - 'font-medium'
  - 'font-mono'
  - 'font-normal'
  - 'font-sans'
  - 'font-semibold'
  - 'font-serif'
  - 'font-thin'

But if you introduce this little custom utility:

```css
@theme {
  --custom-font-weights-foo: 123;
}

@utility font-* {
  --my-weight: --value(--custom-font-weights- *);
}
```

- `font-` would suggest:
  - 'font-foo'

With this fix, we would suggest:

- `font-` would suggest:
  - 'font-black'
  - 'font-bold'
  - 'font-extrabold'
  - 'font-extralight'
  - 'font-foo'          // This is now added
  - 'font-light'
  - 'font-medium'
  - 'font-mono'
  - 'font-normal'
  - 'font-sans'
  - 'font-semibold'
  - 'font-serif'
  - 'font-thin'

We also make sure that they are unique, so if you have a custom utility
that happens to match another existing utility (e.g. `font-bold`), you
won't see `font-bold` twice in the suggestions.

```css
@theme {
  --custom-font-weights-bold: bold;
  --custom-font-weights-normal: normal;
  --custom-font-weights-foo: 1234;
}

@utility font-* {
  --my-weight: --value(--custom-font-weights-*);
}
```

- `font-` would suggest:
  - 'font-black'
  - 'font-bold'          // Overlaps with existing utility
  - 'font-extrabold'
  - 'font-extralight'
  - 'font-foo'           // This is now added
  - 'font-light'
  - 'font-medium'
  - 'font-mono'
  - 'font-normal'        // Overlaps with existing utility
  - 'font-sans'
  - 'font-semibold'
  - 'font-serif'
  - 'font-thin'
2025-09-08 12:18:30 +02:00
Robin Malfait
77b3cb5318
Handle @variant inside @custom-variant (#18885)
This PR fixes an issue where you cannot use `@variant` inside a
`@custom-variant`. While you can use `@variant` in normal CSS, you
cannot inside of `@custom-variant`. Today this silently fails and emits
invalid CSS.
```css
@custom-variant dark {
  @variant data-dark {
    @slot;
  }
}
```
```html
<div class="dark:flex"></div>
```

Would result in:
```css
.dark\:flex {
  @variant data-dark {
    display: flex;
  }
}
```

To solve it we have 3 potential solutions:

1. Consider it user error — but since it generates CSS and you don't
really get an error you could be shipping broken CSS unknowingly.
1. We could try and detect this and not generate CSS for this and
potentially show a warning.
1. We could make it work as expected — which is what this PR does.

Some important notes:

1. The evaluation of the `@custom-variant` only happens when you
actually need it. That means that `@variant` inside `@custom-variant`
will always have the implementation of the last definition of that
variant.

In other words, if you use `@variant hover` inside a `@custom-variant`,
and later you override the `hover` variant, the `@custom-variant` will
use the new implementation.
1. If you happen to introduce a circular dependency, then an error will
be thrown during the build step.

You can consider it a bug fix or a new feature it's a bit of a gray
area. But
one thing that is cool about this is that you can ship a plugin that
looks like
this:
```css
@custom-variant hocus {
  @variant hover {
    @slot;
  }

  @variant focus {
    @slot;
  }
}
```

And it will use the implementation of `hover` and `focus` that the user
has defined. So if they have a custom `hover` or `focus` variant it will
just work.

By default `hocus:underline` would generate:
```css
@media (hover: hover) {
  .hocus\:underline:hover {
    text-decoration-line: underline;
  }
}

.hocus\:underline:focus {
  text-decoration-line: underline;
}
```

But if you have a custom `hover` variant like:
```css
@custom-variant hover (&:hover);
```

Then `hocus:underline` would generate:
```css
.hocus\:underline:hover, .hocus\:underline:focus {
  text-decoration-line: underline;
}
```

### Test plan

1. Existing tests pass
2. Added tests with this new functionality handled
3. Made sure to add a test for circular dependencies + error message
4. Made sure that if you "fix" the circular dependency (by overriding a
variant) that everything is generated as expected.

Fixes: https://github.com/tailwindlabs/tailwindcss/issues/18524
2025-09-05 12:24:11 +00:00
Robin Malfait
274be93fd8
Handle ' syntax in ClojureScript when extracting classes (#18888)
This PR fixes an issue where the `'` syntax in ClojureScript was not
handled properly, resulting in missing extracted classes.

This PR now supports the following ClojureScript syntaxes:

```cljs
; Keyword
(print 'text-red-500)

; List
(print '(flex flex-col underline))

; Vector
(print '[flex flex-col underline])
```

### Test plan

1. Added regression tests
2. Verified that we extract classes correctly now in various scenarios:

Top is before, bottom is with this PR:

<img width="1335" height="1862" alt="image"
src="https://github.com/user-attachments/assets/746aa073-25f8-41f8-b71c-ba83a33065aa"
/>


Fixes: #18882
2025-09-05 14:17:07 +02:00
Jordan Pittman
1334c99db8
Prepare v4.1.13 release (#18868) 2025-09-04 13:18:25 -04:00
Robin Malfait
65dc530f05
Do not allow variants to end with - or _ (#18872)
This PR is a followup of #18867, but this time we won't allow
`@custom-variant` to end with `-` or `_`.

The same reasoning applies here where Oxide doesn't pick this up but
Intellisense and Tailwind CSS' core does.

---------

Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
2025-09-03 15:30:54 +00:00
Robin Malfait
54c3f308e9
Do not allow variants to start with - (#18867)
This PR fixes an issue where custom variants with just `-` in the name
were allowed but weren't actually picked up by Oxide so you couldn't use
them anyway.

The reason we allow `-` is for `kebab-style-variants`, which is very
common, but you shouldn't use `-`, `--` or more in a variant name.

It doesn't really solve the issue (#18863), but it fixes the
inconsistencies in that exist today.

Inconsistencies:
| &nbsp; | `-:flex` | `--:flex` |
| --: | :--: | :--: |
| Oxide | ❌ | ❌ |
| Tailwind Play | ✅ | ❌ |
| Intellisense | ✅ | ✅ |

- Oxide already had the correct rules setup, so this is expected
- Tailwind Play uses Tailwind's core compile step, but it considers
candidates that start with `--` as a CSS variable instead of a utility.
This means that the `--:flex` was considered a CSS variable and skipped
during compilation.
- Intellisense uses the same APIs than Tailwind's core, but it didn't
have the CSS variable check which resulted in the `--:flex` being
"correct".

With this PR, the matrix looks like this now:
| &nbsp; | `-:flex` | `--:flex` |
| --: | :--: | :--: |
| Oxide | ❌ | ❌ |
| Tailwind Play | ❌ | ❌ |
| Intellisense | ❌ | ❌ |


This should not be considered a breaking change because Oxide didn't
pick up candidates with variants that start with a `-`. CSS for these
candidates was never generated before.

Closes: #18863

---------

Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
2025-09-03 14:32:18 +00:00
Robin Malfait
494051ca08
Consider variants starting with @- to be invalid (e.g. @-2xl:flex) (#18869)
This PR fixes a small parsing issue where variants such as `@-2xl:flex`
would parse, but were handled as-if they were `@2xl:flex` instead.

Noticed this while working on: #18867 

This is because when we parse normal variants like `data-foo` then we
want to have a `data` root and a `foo` value, not a `-foo` value.

If you are now using `@-2xl:flex`, then no CSS will be generated for
this anymore. If you were relying on this for some reason, you should
use `@2xl:flex` instead.

## Test plan

Before:

<img width="862" height="586" alt="image"
src="https://github.com/user-attachments/assets/b5993ca6-f907-49af-b5bd-b7206c8300e1"
/>

After:

<img width="862" height="586" alt="image"
src="https://github.com/user-attachments/assets/351f45e4-4cd3-451c-ae2a-c52c3e770629"
/>

---------

Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
2025-09-03 14:07:32 +00:00
Robin Malfait
4637069087
update changelog 2025-09-02 11:26:17 +02:00
Niklas Mischkulnig
664f2e36da
Add '.vercel' to ignored content directories (#18855)
Just like `.next` is ignored, ignore `.vercel` as well.
2025-09-02 09:26:06 +00:00
hustrust
c318329a1e
chore: remove redundant words (#18853) 2025-09-02 08:13:15 +00:00
Robin Malfait
ddc84b079b
update test after prettier change 2025-09-01 11:57:54 +02:00
Robin Malfait
f1331a857a
run prettier 2025-09-01 11:42:37 +02:00
okonomi
e5513b6c75
Fix missing code block delimiters in comment blocks (#18837)
## Summary

I fixed some code blocks inside comment blocks that were missing
delimiters.
2025-09-01 09:36:43 +00:00
depfu[bot]
fed6c6ab72
🚨 [security] Update all of nextjs 15.4.4 → 15.4.7 (patch) (#18838)
<hr>

🚨 <b>Your current dependencies have known security vulnerabilities</b> 🚨

This dependency update fixes known security vulnerabilities. Please see
the details below and assess their impact carefully. We recommend to
merge and deploy this as soon as possible!
<hr>



Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint-config-next (15.4.4 → 15.4.7)





Sorry, we couldn't find anything useful about this release.



#### ✳️ next (15.4.4 → 15.4.7) ·
[Repo](https://github.com/vercel/next.js)


<details>

<summary>Security Advisories 🚨</summary>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-xv57-4mr9-wg8v">🚨
Next.js Content Injection Vulnerability for Image Optimization</a></h4>
<blockquote><p dir="auto">A vulnerability in <strong>Next.js Image
Optimization</strong> has been fixed in <strong>v15.4.5</strong> and
<strong>v14.2.31</strong>. The issue allowed attacker-controlled
external image sources to trigger file downloads with arbitrary content
and filenames under specific configurations. This behavior could be
abused for phishing or malicious file delivery.</p>
<p dir="auto">All users relying on <code
class="notranslate">images.domains</code> or <code
class="notranslate">images.remotePatterns</code> are encouraged to
upgrade and verify that external image sources are strictly
validated.</p>
<p dir="auto">More details at <a
href="https://vercel.com/changelog/cve-2025-55173">Vercel
Changelog</a></p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-g5qg-72qw-gw5v">🚨
Next.js Affected by Cache Key Confusion for Image Optimization API
Routes</a></h4>
<blockquote><p dir="auto">A vulnerability in Next.js Image Optimization
has been fixed in v15.4.5 and v14.2.31. When images returned from API
routes vary based on request headers (such as <code
class="notranslate">Cookie</code> or <code
class="notranslate">Authorization</code>), these responses could be
incorrectly cached and served to unauthorized users due to a cache key
confusion bug.</p>
<p dir="auto">All users are encouraged to upgrade if they use API routes
to serve images that depend on request headers and have image
optimization enabled.</p>
<p dir="auto">More details at <a
href="https://vercel.com/changelog/cve-2025-57752">Vercel
Changelog</a></p></blockquote>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-4342-x723-ch2f">🚨
Next.js Improper Middleware Redirect Handling Leads to SSRF</a></h4>
<blockquote><p dir="auto">A vulnerability in <strong>Next.js
Middleware</strong> has been fixed in <strong>v14.2.32</strong> and
<strong>v15.4.7</strong>. The issue occurred when request headers were
directly passed into <code
class="notranslate">NextResponse.next()</code>. In self-hosted
applications, this could allow Server-Side Request Forgery (SSRF) if
certain sensitive headers from the incoming request were reflected back
into the response.</p>
<p dir="auto">All users implementing custom middleware logic in
self-hosted environments are strongly encouraged to upgrade and verify
correct usage of the <code class="notranslate">next()</code>
function.</p>
<p dir="auto">More details at <a
href="https://vercel.com/changelog/cve-2025-57822">Vercel
Changelog</a></p></blockquote>
</details>

<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.4.7">15.4.7</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon
octicon-info mr-2" viewbox="0 0 16 16" version="1.1" width="16"
height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0
8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1
7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1
0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0
2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>fix router handling when setting a location response header <a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/82588">#82588</a>
</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to <a
href="https://bounce.depfu.com/github.com/ztanner">@ztanner</a> for
helping!</p></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.4.6">15.4.6</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon
octicon-info mr-2" viewbox="0 0 16 16" version="1.1" width="16"
height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0
8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1
7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1
0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0
2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>fix: <code class="notranslate">_error</code> page's <code
class="notranslate">req.url</code> can be overwritten to dynamic param
on minimal mode (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/82347">#82347</a>)</li>
<li>fix: add <code class="notranslate">?dpl</code> to fonts in <code
class="notranslate">/_next/static/media</code> (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/82384">#82384</a>)</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to <a
href="https://bounce.depfu.com/github.com/devjiwonchoi">@devjiwonchoi</a>,
<a href="https://bounce.depfu.com/github.com/ijjk">@ijjk</a>, and <a
href="https://bounce.depfu.com/github.com/styfle">@styfle</a> for
helping!</p></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.4.5">15.4.5</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon
octicon-info mr-2" viewbox="0 0 16 16" version="1.1" width="16"
height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0
8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1
7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1
0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0
2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>Fix API stripping JSON incorrectly (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/82062">#82062</a>)</li>
<li>Fix i18n fallback: false collision (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/82158">#82158</a>)</li>
<li>Revert "Fix tracing of server actions imported by client components
(<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/82167">#82167</a>)</li>
<li>Ensure setAssetPrefix updates config instance (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/82165">#82165</a>)</li>
<li>Turbopack: update mimalloc (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/82166">#82166</a>)</li>
<li>fix(next/image): fix image-optimizer.ts headers (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/82175">#82175</a>)</li>
<li>fix(next/image): improve and simplify detect-content-type (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/82174">#82174</a>)</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to <a
href="https://bounce.depfu.com/github.com/ijjk">@ijjk</a>, <a
href="https://bounce.depfu.com/github.com/sokra">@sokra</a>, and <a
href="https://bounce.depfu.com/github.com/styfle">@styfle</a> for
helping!</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="fe5db65859...f30d815859">See
the full diff on Github</a>. The new version differs by 14 commits:</p>
<ul>
<li><a
href="f30d815859"><code>v15.4.7</code></a></li>
<li><a
href="1a026e338d"><code>fix
router handling when setting a location response header
(#82588)</code></a></li>
<li><a
href="be4aafd4b7"><code>v15.4.6</code></a></li>
<li><a
href="91e5b6b84f"><code>Backport
&quot;fix: add `?dpl` to fonts in `/_next/static/media` (#82384)&quot;
(#82421)</code></a></li>
<li><a
href="f1629d9395"><code>Backport
&quot;[Pages] fix: `_error` page&#39;s `req.url` can be overwritten t…
(#82377)</code></a></li>
<li><a
href="b9aab5dbe9"><code>v15.4.5</code></a></li>
<li><a
href="a8c93c49dd"><code>Disable
test new tests jobs</code></a></li>
<li><a
href="ed2a6c7548"><code>[backport]:
fix(next/image): improve and simplify detect-content-type (#82118)
(#82174)</code></a></li>
<li><a
href="f00fcc9011"><code>[backport]:
fix(next/image): fix image-optimizer.ts headers (#82114)
(#82175)</code></a></li>
<li><a
href="55a7568e9d"><code>Backport:
Turbopack: update mimalloc (#81993) (#82166)</code></a></li>
<li><a
href="5bc4b368e5"><code>[backport]
Ensure setAssetPrefix updates config instance (#82165)</code></a></li>
<li><a
href="717dfb6ec9"><code>[Backport]
Revert &quot;Fix tracing of server actions imported by client components
(#78968) (#82167)</code></a></li>
<li><a
href="6372ba03e8"><code>[backport]
Fix i18n fallback: false collision (#82158)</code></a></li>
<li><a
href="1e2c3792f8"><code>Fix
API stripping JSON incorrectly (#82062)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-09-01 11:24:56 +02:00
Jordan Pittman
5e2a160d8b
Drop exact duplicate declarations from output CSS within a style rule (#18809)
Fixes #18178

When someone writes a utility like `after:content-['foo']` it'll produce
duplicate `content: var(--tw-content)` declarations. I thought about
special casing these but we already have an optimization pass where we
perform a full walk of the AST, flattening some rules (with the `&`
selector), analyzing declarations, etc… We can utilize that existing
spot in core to analyze and remove duplicate declarations within rules
across the AST.

The implementation does this by keeping track of declarations within a
style rule and keeps the last one for any *exact duplicate* which is a
tuple of `(property, value, important)`. This does require some
additional loops but preseving the *last* declaration is important for
correctness with regards to CSS nesting.

For example take this nested CSS:
```css
.foo {
  color: red;
  & .bar {
    color: green;
  }
  color: red;
}
```

It expands to this:
```css
.foo {
  color: red;
}
.foo.bar {
  color: green;
}
.foo {
  color: red;
}
```

If you remove the *last* rule then a `<div class="foo bar">…</div>` will
have green text when its supposed to be red. Since that would affect
behavior we have to always preserve the last declaration for a given
property.

We could go further and eliminate multiple declarations for the same
property *but* this presents a problem: every property and value must be
understood and combined with browser targets to understand whether or
not that property may act as a "fallback" or whether definitely
overwrites its previous value in all cases. This is a much more
complicated task that is much more suited to something light Lighting
CSS.
2025-08-29 11:01:56 -04:00
Jordan Pittman
b1fb02a2d7
Hide internal fields from completions in matchUtilities (#18820)
The `__CSS_VALUES__` field is an internal field we use to transport data
about theme options from CSS throug hte JS plugin API. It wasn’t
supposed to show up in suggestions but we forgot to remove it from them.

Fixes #18812
2025-08-29 11:01:20 -04:00
depfu[bot]
1602e7866d
Update magic-string 0.30.17 → 0.30.18 (minor) (#18821)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ magic-string (0.30.17 → 0.30.18) ·
[Repo](https://github.com/rich-harris/magic-string) ·
[Changelog](https://github.com/Rich-Harris/magic-string/blob/master/CHANGELOG.md)




<details>
<summary>Commits</summary>
<p><a
href="5ce04aa19d...0005025c18">See
the full diff on Github</a>. The new version differs by 5 commits:</p>
<ul>
<li><a
href="0005025c18"><code>chore:
release v0.30.18</code></a></li>
<li><a
href="376bafcb30"><code>chore:
update package.json meta</code></a></li>
<li><a
href="e59c925eb8"><code>chore:
update deps</code></a></li>
<li><a
href="0fd6253e3e"><code>fix:
prevent infinite loop on empty input (#302)</code></a></li>
<li><a
href="a8ee7b79c0"><code>chore:
update eslint config</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-08-29 14:22:19 +00:00
depfu[bot]
5ca129099c
Update @playwright/test 1.54.2 → 1.55.0 (minor) (#18823)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​playwright/test (1.54.2 → 1.55.0) ·
[Repo](https://github.com/Microsoft/playwright)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/microsoft/playwright/releases/tag/v1.55.0">1.55.0</a></h4>

<blockquote><h2 dir="auto">New APIs</h2>
<ul dir="auto">
<li>New Property <a
href="https://playwright.dev/docs/api/class-teststepinfo#test-step-info-title-path">testStepInfo.titlePath</a>
Returns the full title path starting from the test file, including test
and step titles.</li>
</ul>
<h2 dir="auto">Codegen</h2>
<ul dir="auto">
<li>Automatic <code class="notranslate">toBeVisible()</code> assertions:
Codegen can now generate automatic <code
class="notranslate">toBeVisible()</code> assertions for common UI
interactions. This feature can be enabled in the Codegen settings
UI.</li>
</ul>
<h2 dir="auto">Breaking Changes</h2>
<ul dir="auto">
<li>
<g-emoji class="g-emoji" alias="warning">⚠️</g-emoji> Dropped support
for Chromium extension manifest v2.</li>
</ul>
<h2 dir="auto">Miscellaneous</h2>
<ul dir="auto">
<li>Added support for Debian 13 "Trixie".</li>
</ul>
<h2 dir="auto">Browser Versions</h2>
<ul dir="auto">
<li>Chromium 140.0.7339.16</li>
<li>Mozilla Firefox 141.0</li>
<li>WebKit 26.0</li>
</ul>
<p dir="auto">This version was also tested against the following stable
channels:</p>
<ul dir="auto">
<li>Google Chrome 139</li>
<li>Microsoft Edge 139</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="00ce6a8b72...f992162f04">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-08-29 16:16:39 +02:00
Mateusz Bronis
7b03aca53f
Replace deprecated clip with clip-path in sr-only (#18769)
## Summary

This PR replaces the deprecated `clip` property used in the `sr-only`
utility with `clip-path`, and updates the corresponding reset in
`not-sr-only`.

- Closes
[tailwindlabs/tailwindcss#18768](https://github.com/tailwindlabs/tailwindcss/issues/18768)
- Replaces `clip: rect(0, 0, 0, 0);` with `clip-path: inset(50%);` in
`sr-only`
- Replaces `clip: auto;` with `clip-path: none;` in `not-sr-only`
- Updates unit test snapshots to reflect the new CSS output

Rationale:

- `clip` is deprecated and flagged by modern linters; `clip-path` is the
recommended modern alternative while preserving the intended
visually-hidden behavior.

Before:

```css
.sr-only {
  clip: rect(0, 0, 0, 0);
}

.not-sr-only {
  clip: auto;
}
```

After:

```css
.sr-only {
  clip-path: inset(50%);
}

.not-sr-only {
  clip-path: none;
}
```

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-08-28 11:05:38 -04:00
Robin Malfait
e578238da5
Migrate supports theme keys (#18817)
This PR is a follow up of #18815 and #18816, but this time let's migrate
the `supports` theme keys.

Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
  content: ['./src/**/*.html'],
  theme: {
    extend: {
      supports: {
        // Automatically handled by bare values (using CSS variable as the value)
        foo: 'foo: var(--foo)', // parentheses are optional
        bar: '(bar: var(--bar))',

        // Not automatically handled because names differ
        baz: 'qux: var(--foo)',
   //   ^^^   ^^^       ← different names

        // Custom
        grid: 'display: grid',
      },
    },
  },
}
```

Then we would generate the following Tailwind CSS v4 CSS:

```css
@custom-variant supports-baz {
  @supports (qux: var(--foo)) {
    @slot;
  }
}
@custom-variant supports-grid {
  @supports (display: grid) {
    @slot;
  }
}
```

Notice how we didn't generate a custom variant for `data-foo` or
`data-bar` because those are automatically handled by bare values.

I also went with the longer form of `@custom-variant`, we could use the
single selector approach, but that felt less clear to me.

```css
@custom-variant supports-baz (@supports (qux: var(--foo)));
@custom-variant supports-grid (@supports (display: grid));
```

---------

Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
2025-08-28 14:50:48 +00:00
Robin Malfait
82034ec327
Migrate data theme keys (#18816)
This PR is similar to and a follow up of #18815, but this time to
migrate the `data` theme keys.

Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
  content: ['./src/**/*.html'],
  theme: {
    extend: {
      data: {
        // Automatically handled by bare values
        foo: 'foo',
    //  ^^^   ^^^       ← same names

        // Not automatically handled by bare values
        bar: 'baz',
    //  ^^^   ^^^       ← different names

        // Completely custom
        checked: 'ui~="checked"',
      },
    },
  },
}
```

Then we would generate the following Tailwind CSS v4 CSS:

```css
@custom-variant data-bar (&[data-baz]);
@custom-variant data-checked (&[data-ui~="checked"]);
```

Notice how we didn't generate a custom variant for `data-foo` because
those are automatically handled by bare values.
2025-08-28 14:45:18 +00:00
Robin Malfait
9e498a3e78
Migrate aria theme keys (#18815)
This PR migrates `aria` theme keys when migrating from Tailwind CSS v3
to v4.

While working on improving some of the error messages to get more
insights into why migrating the JS file changed
(https://github.com/tailwindlabs/tailwindcss/pull/18808), I ran into an
issue where I couldn't think of a good comment to why `aria` theme keys
were not being migrated. (Internally we have `aria` "blocked").

So instead of figuring out a good error message..., I just went ahead
and added the migration for `aria` theme keys.


Let's imagine you have the following Tailwind CSS v3 configuration:
```ts
export default {
  content: ['./src/**/*.html'],
  theme: {
    extend: {
      aria: {
        // Built-in (not really, but visible because of intellisense)
        busy: 'busy="true"',

        // Automatically handled by bare values
        foo: 'foo="true"',
    //  ^^^   ^^^            ← same names

        // Not automatically handled by bare values because names differ
        bar: 'baz="true"',
    //  ^^^   ^^^            ← different names

        // Completely custom
        asc: 'sort="ascending"',
        desc: 'sort="descending"',
      },
    },
  },
}
```

Then we would generate the following Tailwind CSS v4 CSS:

```css
@custom-variant aria-bar (&[aria-baz="true"]);
@custom-variant aria-asc (&[aria-sort="ascending"]);
@custom-variant aria-desc (&[aria-sort="descending"]);
```

Notice how we didn't generate a custom variant for `aria-busy` or
`aria-foo` because those are automatically handled by bare values.

We could also emit a comment near the CSS to warn about the fact that
`@custom-variant` will always be sorted _after_ any other built-in
variants.

This could result in slightly different behavior, or different order of
classes when using `prettier-plugin-tailwindcss`.

I don't know how important this is, because before this PR we would just
use `@config './tailwind.config.js';`.
Edit: when using the `@config` we override `aria` and extend it, which
means that it would be in the expected order 🤔

---------

Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
2025-08-28 16:40:38 +02:00
Jordan Pittman
8165e04564
Show suggestions for known matchVariant values (#18798)
Given this variant:
```js
matchVariant(
  "foo",
  (value) => `&:is([data-foo='${value}'])`,
  {
    values: {
      DEFAULT: "",
      bar: "bar",
      baz: "bar",
    },
  }
)
```

We weren't listing `foo-bar` and `foo-baz` in IntelliSense. This PR
fixes that.
2025-08-26 14:25:35 +00:00
Jordan Pittman
ee987e3f6a
Discard matchVariant matches with unknown named values (#18799)
This PR fixes two issues:
- When a variant is defined by `matchVariant` it could match unknown
values but not apply the variant (because it's unknown). This would
result in a utility being output that is the _same_ as a bare utility
without variants but a longer name. These were intended to be discarded
but weren't done so correctly.
- Similarly, when we encounter a known value but its not a string the
same thing would happen where we'd output a utility without applying the
variant. This was also intended to be discarded.

Basically given this code:
```js
matchVariant(
  "foo",
  (value) => `&:is([data-foo='${value}'])`,
  {
    values: {
      DEFAULT: "",
      bar: "bar",
      obj: { some: "object" },
    },
  }
)
```

And this HTML:
```html
<div class="foo-bar:bg-none foo-[baz]:bg-none foo-baz:bg-none foo-obj:bg-none"></div>
```

This CSS would be produced:
```css
@layer utilities {
  .foo-bar\:bg-none {
    &:is([data-foo='bar']) {
      background-image: none;
    }
  }
  /* this one shouldn't be here */
  .foo-baz\:bg-none {
    background-image: none;
  }
  /* this one shouldn't be here */
  .foo-obj\:bg-none {
    background-image: none;
  }
  .foo-\[baz\]\:bg-none {
    &:is([data-foo='baz']) {
      background-image: none;
    }
  }
}
```
2025-08-26 10:21:15 -04:00
Jordan Pittman
ce9b290b6b
Don't transition visibility when using transition (#18795)
We introduced an accidental breaking change a few months ago in 4.1.5
with #17812.

We added `visibility` to the property list in `transition` which
unfortunately only applies its change instantly when going from
invisible -> visible.

I've checked `display`, `content-visibility`, and `pointer-events` and
they apply their change instantly (as best I can tell) when
transitioning by default. And `overlay` only "applies" for discrete
transitions so it can stay as well.

The spec has this to say about [animating
`visibility`](https://www.w3.org/TR/web-animations-1/#animating-visibility):
> For the visibility property, visible is interpolated as a discrete
step where values of p between 0 and 1 map to visible and other values
of p map to the closer endpoint; if neither value is visible then
discrete animation is used.

This means that for visible (t=0) -> hidden (t=1) the timeline looks
like this:
- t=0.0: visible
- t=0.5: visible
- t=0.999…8: visible
- t=1.0: invisible

This means that for invisible (t=0) -> visible (t=1) the timeline looks
like this:
- t=0.0: invisible
- t=0.000…1: visible
- t=0.5: visible
- t=1.0: visible

So the value *is* instantly applied if the element is initially
invisible but when going the other direction this is not the case. This
happens whether or not the transition type is discrete.

While the spec calls out [`display` as working
similarly](https://drafts.csswg.org/css-display-4/#display-animation) in
practice this is only the case when `transition-behavior` is explicitly
set to `allow-discrete` otherwise the change is instant for both
directions.

Fixes #18793
2025-08-25 14:30:56 -04:00
depfu[bot]
e4c0255e3a
Update eslint 9.32.0 → 9.33.0 (minor) (#18779)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.32.0 → 9.33.0) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.33.0">9.33.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="e07820e66f"><code
class="notranslate">e07820e</code></a> feat: add global object access
detection to no-restricted-globals (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19939">#19939</a>)
(sethamus)</li>
<li>
<a
href="90b050ec11"><code
class="notranslate">90b050e</code></a> feat: support explicit resource
management in <code class="notranslate">one-var</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19941">#19941</a>)
(Sweta Tanwar)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="732433c4fb"><code
class="notranslate">732433c</code></a> fix: allow any type for <code
class="notranslate">meta.docs.recommended</code> in custom rules (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19995">#19995</a>)
(Francesco Trotta)</li>
<li>
<a
href="e8a6914a24"><code
class="notranslate">e8a6914</code></a> fix: Fixed potential bug in
check-emfile-handling.js (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19975">#19975</a>)
(諏訪原慶斗)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="34f0723e2d"><code
class="notranslate">34f0723</code></a> docs: playground button for
TypeScript code example (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19671">#19671</a>)
(Tanuj Kanti)</li>
<li>
<a
href="dc942a47da"><code
class="notranslate">dc942a4</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="5a4b6f7432"><code
class="notranslate">5a4b6f7</code></a> docs: Update no-multi-assign.md
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19979">#19979</a>)
(Yuki Takada (Yukinosuke Takada))</li>
<li>
<a
href="247e15698e"><code
class="notranslate">247e156</code></a> docs: add missing let
declarations in <code class="notranslate">no-plusplus</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19980">#19980</a>)
(Yuki Takada (Yukinosuke Takada))</li>
<li>
<a
href="0d17242b3c"><code
class="notranslate">0d17242</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="fa20b9db8f"><code
class="notranslate">fa20b9d</code></a> docs: Clarify when to open an
issue for a PR (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19974">#19974</a>)
(Nicholas C. Zakas)</li>
</ul>
<h2 dir="auto">Build Related</h2>
<ul dir="auto">
<li>
<a
href="27fa86551b"><code
class="notranslate">27fa865</code></a> build: use <code
class="notranslate">ESLint</code> class to generate formatter examples
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19972">#19972</a>)
(Milos Djermanovic)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="425804602e"><code
class="notranslate">4258046</code></a> chore: update dependency
@eslint/js to v9.33.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19998">#19998</a>)
(renovate[bot])</li>
<li>
<a
href="ad283717ed"><code
class="notranslate">ad28371</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="06a22f154c"><code
class="notranslate">06a22f1</code></a> test: resolve flakiness in --mcp
flag test (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19993">#19993</a>)
(Pixel998)</li>
<li>
<a
href="54920ed229"><code
class="notranslate">54920ed</code></a> test: switch to <code
class="notranslate">Linter.Config</code> in <code
class="notranslate">ESLintRules</code> type tests (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19977">#19977</a>)
(Francesco Trotta)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="2364031090...a90d7c4fe5">See
the full diff on Github</a>. The new version differs by 17 commits:</p>
<ul>
<li><a
href="a90d7c4fe5"><code>9.33.0</code></a></li>
<li><a
href="9534b55372"><code>Build:
changelog update for 9.33.0</code></a></li>
<li><a
href="425804602e"><code>chore:
update dependency @eslint/js to v9.33.0 (#19998)</code></a></li>
<li><a
href="ad283717ed"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="06a22f154c"><code>test:
resolve flakiness in --mcp flag test (#19993)</code></a></li>
<li><a
href="732433c4fb"><code>fix:
allow any type for `meta.docs.recommended` in custom rules
(#19995)</code></a></li>
<li><a
href="34f0723e2d"><code>docs:
playground button for TypeScript code example (#19671)</code></a></li>
<li><a
href="dc942a47da"><code>docs:
Update README</code></a></li>
<li><a
href="5a4b6f7432"><code>docs:
Update no-multi-assign.md (#19979)</code></a></li>
<li><a
href="247e15698e"><code>docs:
add missing let declarations in `no-plusplus` (#19980)</code></a></li>
<li><a
href="0d17242b3c"><code>docs:
Update README</code></a></li>
<li><a
href="e07820e66f"><code>feat:
add global object access detection to no-restricted-globals
(#19939)</code></a></li>
<li><a
href="fa20b9db8f"><code>docs:
Clarify when to open an issue for a PR (#19974)</code></a></li>
<li><a
href="54920ed229"><code>test:
switch to `Linter.Config` in `ESLintRules` type tests
(#19977)</code></a></li>
<li><a
href="e8a6914a24"><code>fix:
Fixed potential bug in check-emfile-handling.js (#19975)</code></a></li>
<li><a
href="90b050ec11"><code>feat:
support explicit resource management in `one-var`
(#19941)</code></a></li>
<li><a
href="27fa86551b"><code>build:
use `ESLint` class to generate formatter examples
(#19972)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-08-22 12:47:01 -04:00
depfu[bot]
7779d3d080
Update @vitejs/plugin-react 4.7.0 → 5.0.0 (major) (#18738)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (4.7.0 → 5.0.0) ·
[Repo](https://github.com/vitejs/vite-plugin-react) ·
[Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
















---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-08-15 07:37:08 -04:00
zhe he
48f66dc835
Drop warning from browser build (#18732)
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-08-14 12:20:19 -04:00
Robin Malfait
6791e8133c
Prepare v4.1.12 release (#18728)
Co-authored-by: Jordan Pittman <thecrypticace@gmail.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-08-14 14:35:49 +02:00
Robin Malfait
88a8234c8a
Mark .hdr and .exr as binary extensions (#18734)
This PR fixes an issue where `.hdr` files were scanned for candidates
even though it's a binary file.

As a workaround today, you could use:

```css
@source not "**/*.hdr";
```

To ignore `.hdr` files, but let's bake it in by default instead.

Fixes: #18733
2025-08-14 14:32:30 +02:00
Bill Criswell
1855d68cd7
Add --border-color to divide theme keys (#18704)
## Summary

In Tailwind 3 the border colors were able to be used with `divide`
utilities. I made it so that's true for Tailwind 4.

## Test plan

Just used `pnpm run tdd` and making it fails then making sure it passes.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-08-13 16:44:44 +02:00
depfu[bot]
42d2433ab8
Update enhanced-resolve 5.18.2 → 5.18.3 (patch) (#18726)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ enhanced-resolve (5.18.2 → 5.18.3) ·
[Repo](https://github.com/webpack/enhanced-resolve)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/webpack/enhanced-resolve/releases/tag/v5.18.3">5.18.3</a></h4>

<blockquote><h3 dir="auto">Fixes</h3>
<ul dir="auto">
<li>Fixed nonsensible intersection in types</li>
</ul>
<h3 dir="auto">Performance</h3>
<ul dir="auto">
<li>Decreased initial loading time</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="0bf45033f4...52b61d0f03">See
the full diff on Github</a>. The new version differs by 5 commits:</p>
<ul>
<li><a
href="52b61d0f03"><code>chore(release):
5.18.3</code></a></li>
<li><a
href="ec38ca9851"><code>perf:
decrease initial loading time (#458)</code></a></li>
<li><a
href="5f74295eac"><code>refactor:
update eslint config (#457)</code></a></li>
<li><a
href="86ff2125e9"><code>fix(types):
fix nonsensible intersection</code></a></li>
<li><a
href="367d0f65e6"><code>chore(deps):
bump form-data from 3.0.3 to 3.0.4 (#455)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-08-13 10:30:50 -04:00
Jordan Pittman
6bfdb7c60e
Bump Bun (#18723)
Fixes #18695

Was waiting for 1.2.20 b/c of some build failures. Hopefully fixed now.
Also appears to fix the above linked bug about Windows symlinks.

[ci-all]
2025-08-13 16:29:52 +02:00
depfu[bot]
1bbbe41885
Update all of react 19.1.0 → 19.1.1 (patch) (#18669)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ react (19.1.0 → 19.1.1) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.1.1">19.1.1</a></h4>

<blockquote><h3 dir="auto">React</h3>
<ul dir="auto">
<li>Fixed Owner Stacks to work with ES2015 function.name semantics (<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33680">#33680</a>
by <a href="https://bounce.depfu.com/github.com/hoxyq">@hoxyq</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="4a9df08157...02ef495809">See
the full diff on Github</a>. The new version differs by 11 commits:</p>
<ul>
<li><a
href="02ef495809"><code>Reset
packages we are not releasing to currently published
versions</code></a></li>
<li><a
href="3f178f55fc"><code>[Release]
Update build script to properly set React Native&#39;s renderers version
(#33972)</code></a></li>
<li><a
href="87e33ca2b7"><code>Set
release versions to 19.1.1</code></a></li>
<li><a
href="52cf381c72"><code>[eprh]
Bump stable version (#32978)</code></a></li>
<li><a
href="b793948e15"><code>Bump
next prerelease version numbers (#32782)</code></a></li>
<li><a
href="73e4ba42cd"><code>Allow
runtime_build_and_test action to trigger manually
(#33796)</code></a></li>
<li><a
href="5a1eb6f61a"><code>fix:
rename bottom stack frame (#33680)</code></a></li>
<li><a
href="01eae200bf"><code>[DevTools]
Get source location from structured callsites in prepareStackTrace
(#33143)</code></a></li>
<li><a
href="0e6781a06b"><code>Enable
the `enableEagerAlternateStateNodeCleanup` Feature Flag
(#33447)</code></a></li>
<li><a
href="2cd3c424ea"><code>Add
eager alternate.stateNode cleanup (#33161)</code></a></li>
<li><a
href="a24654e65b"><code>Ship
enableFabricCompleteRootInCommitPhase (#33064)</code></a></li>
</ul>
</details>




#### ✳️ react-dom (19.1.0 → 19.1.1) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.1.1">19.1.1</a></h4>

<blockquote><h3 dir="auto">React</h3>
<ul dir="auto">
<li>Fixed Owner Stacks to work with ES2015 function.name semantics (<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/33680">#33680</a>
by <a href="https://bounce.depfu.com/github.com/hoxyq">@hoxyq</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="4a9df08157...02ef495809">See
the full diff on Github</a>. The new version differs by 11 commits:</p>
<ul>
<li><a
href="02ef495809"><code>Reset
packages we are not releasing to currently published
versions</code></a></li>
<li><a
href="3f178f55fc"><code>[Release]
Update build script to properly set React Native&#39;s renderers version
(#33972)</code></a></li>
<li><a
href="87e33ca2b7"><code>Set
release versions to 19.1.1</code></a></li>
<li><a
href="52cf381c72"><code>[eprh]
Bump stable version (#32978)</code></a></li>
<li><a
href="b793948e15"><code>Bump
next prerelease version numbers (#32782)</code></a></li>
<li><a
href="73e4ba42cd"><code>Allow
runtime_build_and_test action to trigger manually
(#33796)</code></a></li>
<li><a
href="5a1eb6f61a"><code>fix:
rename bottom stack frame (#33680)</code></a></li>
<li><a
href="01eae200bf"><code>[DevTools]
Get source location from structured callsites in prepareStackTrace
(#33143)</code></a></li>
<li><a
href="0e6781a06b"><code>Enable
the `enableEagerAlternateStateNodeCleanup` Feature Flag
(#33447)</code></a></li>
<li><a
href="2cd3c424ea"><code>Add
eager alternate.stateNode cleanup (#33161)</code></a></li>
<li><a
href="a24654e65b"><code>Ship
enableFabricCompleteRootInCommitPhase (#33064)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-08-12 13:59:07 +00:00
Michaël De Boey
8e8a2d6efc
update @ampproject/remapping to @jridgewell/remapping (#18716)
Even though
[`@ampproject/remapping`](https://npm.im/@ampproject/remapping) isn't
deprecated on npm (yet), it's
[repo](https://github.com/ampproject/remapping) is archived, so people
should move to
[`@jridgewell/remapping`](https://npm.im/@jridgewell/remapping)

> Development moved to
[monorepo](https://github.com/jridgewell/sourcemaps)
> See
https://github.com/jridgewell/sourcemaps/tree/main/packages/remapping
for latest code.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-08-12 09:52:35 -04:00
Robin Malfait
30be24b29f
Fix false-positive migrations in addEventListener and JavaScript variable names (#18718)
This PR fixes 2 false-positives when running the upgrade tool on a
Tailwind CSS v3 project converting it to a Tailwind CSS v4 project.

The issue occurs around migrations with short simple names that have a
meaning outside if Tailwind CSS, e.g. `blur` and `outline`.

This PR fixes 2 such cases:


1. The `addEventListener` case:

   ```js
   document.addEventListener('blur', handleBlur)
   ```

We do this by special casing the `addEventListener(` case and making
sure the first argument to `addEventListener` is never migrated.

2. A JavaScript variable with default value:

   ```js
   function foo({ foo = "bar", outline = true, baz = "qux" }) {
     // ...
   }
   ```

The bug is relatively subtle here, but it has actually nothing to do
with `outline` itself, but rather the fact that some quote character
came before and after it on the same line...

One of our heuristics for determining if a migration on these small
words is safe, is to ensure that the candidate is inside of a string.
Since we didn't do any kind of quote balancing, we would consider the
`outline` to be inside of a string, even though it is not.

So to actually solve this, we do some form of quote balancing to ensure
that it's _not_ inside of a string in this case.

Additionally, this PR also introduces a small refactor to the
`is-safe-migration.test.ts` file where we now use a `test.each` to
ensure that failing tests in the middle don't prevent the rest of the
tests from running.

### Test plan

1. Added dedicated tests for the cases mentioned in the issue (#18675).
2. Added a few more tests with various forms of whitespace.

Fixes: #18675
2025-08-12 09:48:35 -04:00
depfu[bot]
3468bcface
Update @emnapi/core 1.4.4 → 1.4.5 (patch) (#18668)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​emnapi/core (1.4.4 → 1.4.5) ·
[Repo](https://github.com/toyobayashi/emnapi)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/toyobayashi/emnapi/releases/tag/v1.4.5">1.4.5</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>fix(wasm32-wasip1-threads): process never exit if trap in threads
(<a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/156">#156</a>)</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/compare/v1.4.4...v1.4.5"><tt>v1.4.4...v1.4.5</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let
us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="87bb86c3a5...4afb0e72e2">See
the full diff on Github</a>. The new version differs by 2 commits:</p>
<ul>
<li><a
href="4afb0e72e2"><code>1.4.5</code></a></li>
<li><a
href="c2ffebf277"><code>fix(wasm32-wasip1-threads):
process never exit if trap in threads (#156)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-08-11 14:30:04 +00:00
depfu[bot]
4f5c820894
Update emnapi 1.4.4 → 1.4.5 (patch) (#18667)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ emnapi (1.4.4 → 1.4.5) ·
[Repo](https://github.com/toyobayashi/emnapi)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/toyobayashi/emnapi/releases/tag/v1.4.5">1.4.5</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>fix(wasm32-wasip1-threads): process never exit if trap in threads
(<a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/156">#156</a>)</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/compare/v1.4.4...v1.4.5"><tt>v1.4.4...v1.4.5</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="87bb86c3a5...4afb0e72e2">See
the full diff on Github</a>. The new version differs by 2 commits:</p>
<ul>
<li><a
href="4afb0e72e2"><code>1.4.5</code></a></li>
<li><a
href="c2ffebf277"><code>fix(wasm32-wasip1-threads):
process never exit if trap in threads (#156)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-08-11 14:24:38 +00:00
depfu[bot]
3333d40249
Update @types/react 19.1.8 → 19.1.9 (patch) (#18673)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react (19.1.8 → 19.1.9) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-08-11 14:17:07 +00:00
depfu[bot]
ea17bff7fe Update @playwright/test to version 1.54.2 2025-08-11 14:16:39 +00:00
depfu[bot]
ffb1d72016
Update @emnapi/runtime 1.4.4 → 1.4.5 (patch) (#18666)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​emnapi/runtime (1.4.4 → 1.4.5) ·
[Repo](https://github.com/toyobayashi/emnapi)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/toyobayashi/emnapi/releases/tag/v1.4.5">1.4.5</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>fix(wasm32-wasip1-threads): process never exit if trap in threads
(<a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/pull/156">#156</a>)</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/toyobayashi/emnapi/compare/v1.4.4...v1.4.5"><tt>v1.4.4...v1.4.5</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="87bb86c3a5...4afb0e72e2">See
the full diff on Github</a>. The new version differs by 2 commits:</p>
<ul>
<li><a
href="4afb0e72e2"><code>1.4.5</code></a></li>
<li><a
href="c2ffebf277"><code>fix(wasm32-wasip1-threads):
process never exit if trap in threads (#156)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-08-11 10:15:53 -04:00
depfu[bot]
ebb2d4395a
Update h3 1.15.3 → 1.15.4 (patch) (#18680)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ h3 (1.15.3 → 1.15.4) · [Repo](https://github.com/h3js/h3) ·
[Changelog](https://github.com/h3js/h3/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/h3js/h3/releases/tag/v1.15.4">1.15.4</a></h4>

<blockquote><p dir="auto"><a
href="https://bounce.depfu.com/github.com/h3js/h3/compare/v1.15.2...v1.15.4">compare
changes</a></p>
<h3 dir="auto">🩹 Fixes</h3>
<ul dir="auto">
<li>
<strong>getRequestHost:</strong> Return first host from <code
class="notranslate">x-forwarded-host</code> (<a
href="https://bounce.depfu.com/github.com/h3js/h3/pull/1175">#1175</a>)</li>
</ul>
<h3 dir="auto">💅 Refactors</h3>
<ul dir="auto">
<li>
<strong>useSession:</strong> Backport <code
class="notranslate">SessionManager</code> interface to fix types (<a
href="https://bounce.depfu.com/github.com/h3js/h3/pull/1058">#1058</a>)</li>
</ul>
<h3 dir="auto">🏡 Chore</h3>
<ul dir="auto">
<li>
<strong>docs:</strong> Fix typos (<a
href="https://bounce.depfu.com/github.com/h3js/h3/pull/1108">#1108</a>)</li>
</ul>
<h3 dir="auto">❤️ Contributors</h3>
<ul dir="auto">
<li>Pooya Parsa (<a
href="https://bounce.depfu.com/github.com/pi0">@pi0</a>)</li>
<li>Kricsleo (<a
href="https://bounce.depfu.com/github.com/kricsleo">@kricsleo</a>)</li>
<li>Izoukhai (<a
href="https://bounce.depfu.com/github.com/izoukhai">@izoukhai</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="5bd27a52d7...ec77d6bc14">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-08-11 10:12:46 -04:00
depfu[bot]
f2b5ed47ba
Update @types/react-dom 19.1.6 → 19.1.7 (patch) (#18674)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react-dom (19.1.6 → 19.1.7) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-08-11 10:02:48 -04:00
depfu[bot]
4d37043fc4
Update eslint 9.30.1 → 9.32.0 (minor) (#18656)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.30.1 → 9.32.0) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.32.0">9.32.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="1245000c5a"><code
class="notranslate">1245000</code></a> feat: support explicit resource
management in core rules (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19828">#19828</a>)
(fnx)</li>
<li>
<a
href="0e957a7b55"><code
class="notranslate">0e957a7</code></a> feat: support typescript types in
accessor rules (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19882">#19882</a>)
(fnx)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="960fd40dfd"><code
class="notranslate">960fd40</code></a> fix: Upgrade @eslint/js (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19971">#19971</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="bbf23fa2f1"><code
class="notranslate">bbf23fa</code></a> fix: Refactor reporting into
FileReport (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19877">#19877</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="d4988872f3"><code
class="notranslate">d498887</code></a> fix: bump @eslint/plugin-kit to
0.3.4 to resolve vulnerability (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19965">#19965</a>)
(Milos Djermanovic)</li>
<li>
<a
href="f46fc6c137"><code
class="notranslate">f46fc6c</code></a> fix: report only global
references in no-implied-eval (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19932">#19932</a>)
(Nitin Kumar)</li>
<li>
<a
href="7863d26b7c"><code
class="notranslate">7863d26</code></a> fix: remove outdated types in
<code class="notranslate">ParserOptions.ecmaFeatures</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19944">#19944</a>)
(ntnyq)</li>
<li>
<a
href="317330552e"><code
class="notranslate">3173305</code></a> fix: update execScript message in
no-implied-eval rule (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19937">#19937</a>)
(TKDev7)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="86e7426e44"><code
class="notranslate">86e7426</code></a> docs: Update README (GitHub
Actions Bot)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="50de1ced9d"><code
class="notranslate">50de1ce</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="74f01a3f59"><code
class="notranslate">74f01a3</code></a> ci: unpin <code
class="notranslate">jiti</code> to version <code
class="notranslate">^2.5.1</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19970">#19970</a>)
(루밀LuMir)</li>
<li>
<a
href="2ab13813a7"><code
class="notranslate">2ab1381</code></a> ci: pin <code
class="notranslate">jiti</code> to version 2.4.2 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19964">#19964</a>)
(Francesco Trotta)</li>
<li>
<a
href="b7f7545469"><code
class="notranslate">b7f7545</code></a> test: switch to flat config mode
in <code class="notranslate">SourceCode</code> tests (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19953">#19953</a>)
(Milos Djermanovic)</li>
<li>
<a
href="f5a35e3b7c"><code
class="notranslate">f5a35e3</code></a> test: switch to flat config mode
in eslint-fuzzer (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19960">#19960</a>)
(Milos Djermanovic)</li>
<li>
<a
href="e22af8c42d"><code
class="notranslate">e22af8c</code></a> refactor: use <code
class="notranslate">CustomRuleDefinitionType</code> in <code
class="notranslate">JSRuleDefinition</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19949">#19949</a>)
(Francesco Trotta)</li>
<li>
<a
href="e85571730f"><code
class="notranslate">e855717</code></a> chore: switch performance tests
to hyperfine (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19919">#19919</a>)
(Francesco Trotta)</li>
<li>
<a
href="2f73a23655"><code
class="notranslate">2f73a23</code></a> test: switch to flat config mode
in <code class="notranslate">ast-utils</code> tests (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19948">#19948</a>)
(Milos Djermanovic)</li>
<li>
<a
href="c565a530f5"><code
class="notranslate">c565a53</code></a> chore: exclude <code
class="notranslate">further_reading_links.json</code> from Prettier
formatting (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19943">#19943</a>)
(Milos Djermanovic)</li>
</ul></blockquote>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.31.0">9.31.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="35cf44c22e"><code
class="notranslate">35cf44c</code></a> feat: output full actual location
in rule tester if different (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19904">#19904</a>)
(ST-DDT)</li>
<li>
<a
href="a6a63259de"><code
class="notranslate">a6a6325</code></a> feat: support explicit resource
management in <code class="notranslate">no-loop-func</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19895">#19895</a>)
(Milos Djermanovic)</li>
<li>
<a
href="4682cdc696"><code
class="notranslate">4682cdc</code></a> feat: support explicit resource
management in <code class="notranslate">no-undef-init</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19894">#19894</a>)
(Milos Djermanovic)</li>
<li>
<a
href="58482165ea"><code
class="notranslate">5848216</code></a> feat: support explicit resource
management in <code class="notranslate">init-declarations</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19893">#19893</a>)
(Milos Djermanovic)</li>
<li>
<a
href="bb370b8e79"><code
class="notranslate">bb370b8</code></a> feat: support explicit resource
management in <code class="notranslate">no-const-assign</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19892">#19892</a>)
(Milos Djermanovic)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="07fac6cafa"><code
class="notranslate">07fac6c</code></a> fix: retry on EMFILE when writing
autofix results (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19926">#19926</a>)
(TKDev7)</li>
<li>
<a
href="28cc7abbb7"><code
class="notranslate">28cc7ab</code></a> fix: Remove incorrect RuleContext
types (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19910">#19910</a>)
(Nicholas C. Zakas)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="664cb44ab0"><code
class="notranslate">664cb44</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="40dbe2a43f"><code
class="notranslate">40dbe2a</code></a> docs: fix mismatch between <code
class="notranslate">globalIgnores()</code> code and text (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19914">#19914</a>)
(MaoShizhong)</li>
<li>
<a
href="5a0069d608"><code
class="notranslate">5a0069d</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="fef04b5c7f"><code
class="notranslate">fef04b5</code></a> docs: Update working on issues
info (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19902">#19902</a>)
(Nicholas C. Zakas)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="3ddd454c1c"><code
class="notranslate">3ddd454</code></a> chore: upgrade to <code
class="notranslate">@eslint/js@9.31.0</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19935">#19935</a>)
(Francesco Trotta)</li>
<li>
<a
href="d5054e5454"><code
class="notranslate">d5054e5</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="0f4a3781fe"><code
class="notranslate">0f4a378</code></a> chore: update eslint (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19933">#19933</a>)
(renovate[bot])</li>
<li>
<a
href="76c2340c36"><code
class="notranslate">76c2340</code></a> chore: bump mocha to v11 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19917">#19917</a>)
(루밀LuMir)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="6769b5fa11...2364031090">See
the full diff on Github</a>. The new version differs by 37 commits:</p>
<ul>
<li><a
href="2364031090"><code>9.32.0</code></a></li>
<li><a
href="a0e62e2739"><code>Build:
changelog update for 9.32.0</code></a></li>
<li><a
href="960fd40dfd"><code>fix:
Upgrade @eslint/js (#19971)</code></a></li>
<li><a
href="50de1ced9d"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="bbf23fa2f1"><code>fix:
Refactor reporting into FileReport (#19877)</code></a></li>
<li><a
href="74f01a3f59"><code>ci:
unpin `jiti` to version `^2.5.1` (#19970)</code></a></li>
<li><a
href="d4988872f3"><code>fix:
bump @eslint/plugin-kit to 0.3.4 to resolve vulnerability
(#19965)</code></a></li>
<li><a
href="2ab13813a7"><code>ci:
pin `jiti` to version 2.4.2 (#19964)</code></a></li>
<li><a
href="b7f7545469"><code>test:
switch to flat config mode in `SourceCode` tests
(#19953)</code></a></li>
<li><a
href="f5a35e3b7c"><code>test:
switch to flat config mode in eslint-fuzzer (#19960)</code></a></li>
<li><a
href="f46fc6c137"><code>fix:
report only global references in no-implied-eval
(#19932)</code></a></li>
<li><a
href="86e7426e44"><code>docs:
Update README</code></a></li>
<li><a
href="e22af8c42d"><code>refactor:
use `CustomRuleDefinitionType` in `JSRuleDefinition`
(#19949)</code></a></li>
<li><a
href="1245000c5a"><code>feat:
support explicit resource management in core rules
(#19828)</code></a></li>
<li><a
href="e85571730f"><code>chore:
switch performance tests to hyperfine (#19919)</code></a></li>
<li><a
href="0e957a7b55"><code>feat:
support typescript types in accessor rules (#19882)</code></a></li>
<li><a
href="2f73a23655"><code>test:
switch to flat config mode in `ast-utils` tests (#19948)</code></a></li>
<li><a
href="7863d26b7c"><code>fix:
remove outdated types in `ParserOptions.ecmaFeatures`
(#19944)</code></a></li>
<li><a
href="c565a530f5"><code>chore:
exclude `further_reading_links.json` from Prettier formatting
(#19943)</code></a></li>
<li><a
href="317330552e"><code>fix:
update execScript message in no-implied-eval rule
(#19937)</code></a></li>
<li><a
href="14053edc64"><code>9.31.0</code></a></li>
<li><a
href="2b77bd05dc"><code>Build:
changelog update for 9.31.0</code></a></li>
<li><a
href="3ddd454c1c"><code>chore:
upgrade to `@eslint/js@9.31.0` (#19935)</code></a></li>
<li><a
href="d5054e5454"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="0f4a3781fe"><code>chore:
update eslint (#19933)</code></a></li>
<li><a
href="664cb44ab0"><code>docs:
Update README</code></a></li>
<li><a
href="07fac6cafa"><code>fix:
retry on EMFILE when writing autofix results (#19926)</code></a></li>
<li><a
href="35cf44c22e"><code>feat:
output full actual location in rule tester if different
(#19904)</code></a></li>
<li><a
href="40dbe2a43f"><code>docs:
fix mismatch between `globalIgnores()` code and text
(#19914)</code></a></li>
<li><a
href="76c2340c36"><code>chore:
bump mocha to v11 (#19917)</code></a></li>
<li><a
href="28cc7abbb7"><code>fix:
Remove incorrect RuleContext types (#19910)</code></a></li>
<li><a
href="a6a63259de"><code>feat:
support explicit resource management in `no-loop-func`
(#19895)</code></a></li>
<li><a
href="4682cdc696"><code>feat:
support explicit resource management in `no-undef-init`
(#19894)</code></a></li>
<li><a
href="58482165ea"><code>feat:
support explicit resource management in `init-declarations`
(#19893)</code></a></li>
<li><a
href="bb370b8e79"><code>feat:
support explicit resource management in `no-const-assign`
(#19892)</code></a></li>
<li><a
href="5a0069d608"><code>docs:
Update README</code></a></li>
<li><a
href="fef04b5c7f"><code>docs:
Update working on issues info (#19902)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-08-01 12:55:57 -04:00
depfu[bot]
7010a9b79b
Update jiti 2.4.2 → 2.5.1 (minor) (#18651)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ jiti (2.4.2 → 2.5.1) · [Repo](https://github.com/unjs/jiti) ·
[Changelog](https://github.com/unjs/jiti/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/unjs/jiti/releases/tag/v2.5.1">2.5.1</a></h4>

<blockquote><p dir="auto"><a
href="https://bounce.depfu.com/github.com/unjs/jiti/compare/v2.5.0...v2.5.1">compare
changes</a></p>
<h3 dir="auto">🩹 Fixes</h3>
<ul dir="auto">
<li>
<strong>interop:</strong> Passthrough module if it is a promise (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/389">#389</a>)</li>
</ul></blockquote>
<h4><a
href="https://github.com/unjs/jiti/releases/tag/v2.5.0">2.5.0</a></h4>

<blockquote><p dir="auto"><a
href="https://bounce.depfu.com/github.com/unjs/jiti/compare/v2.4.2...v2.5.0">compare
changes</a></p>
<h3 dir="auto">🚀 Enhancements</h3>
<ul dir="auto">
<li>Use <code class="notranslate">sha256</code> for cache entries in
FIPS mode (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/375">#375</a>)</li>
<li>Support <code class="notranslate">rebuildFsCache</code> ( <code
class="notranslate">JITI_REBUILD_FS_CACHE</code>) (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/379">#379</a>)</li>
</ul>
<h3 dir="auto">🩹 Fixes</h3>
<ul dir="auto">
<li>Interop modules with null/undefined default export (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/377">#377</a>)</li>
<li>Handle <code class="notranslate">require(&lt;json&gt;)</code> in
register mode (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/374">#374</a>)</li>
</ul>
<h3 dir="auto">📦 Dependencies</h3>
<ul dir="auto">
<li>Updated bundled dependencies (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/compare/v2.4.2...v2.5.0">compare
changes</a>)</li>
</ul>
<h3 dir="auto">📖 Docs</h3>
<ul dir="auto">
<li>Add defaults in JSDocs (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/365">#365</a>)</li>
</ul>
<h3 dir="auto">✅ Tests</h3>
<ul dir="auto">
<li>Only include src for coverage report (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/372">#372</a>)</li>
</ul>
<h3 dir="auto">❤️ Contributors</h3>
<ul dir="auto">
<li>Kricsleo (<a
href="https://bounce.depfu.com/github.com/kricsleo">@kricsleo</a>)
🌟</li>
<li>Pooya Parsa (<a
href="https://bounce.depfu.com/github.com/pi0">@pi0</a>)</li>
<li>Kanon (<a
href="https://bounce.depfu.com/github.com/ysknsid25">@ysknsid25</a>)</li>
<li>Arya Emami (<a
href="https://bounce.depfu.com/github.com/aryaemami59">@aryaemami59</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="340e2a733c...61fa80358b">See
the full diff on Github</a>. The new version differs by 17 commits:</p>
<ul>
<li><a
href="61fa80358b"><code>chore(release):
v2.5.1</code></a></li>
<li><a
href="ad268df55b"><code>fix(interop):
passthrough module if it is a promise (#389)</code></a></li>
<li><a
href="62eac09b15"><code>chore(release):
v2.5.0</code></a></li>
<li><a
href="54461d6061"><code>fix(register):
handle `require(&lt;json&gt;)` (#374)</code></a></li>
<li><a
href="ea2340d1d0"><code>fix:
interop modules with nil default export (#377)</code></a></li>
<li><a
href="2e5a282f4c"><code>feat:
`rebuildFsCache` ( `JITI_REBUILD_FS_CACHE`) (#379)</code></a></li>
<li><a
href="63e907fe35"><code>feat:
use `sha256` for cache entries in fips mode (#375)</code></a></li>
<li><a
href="c567a37af6"><code>chore:
update snapshot</code></a></li>
<li><a
href="fb2da19654"><code>test:
only include src for coverage report (#372)</code></a></li>
<li><a
href="c9fb9fc020"><code>chore(deps):
update autofix-ci/action digest to 635ffb0 (#385)</code></a></li>
<li><a
href="dde7c823fa"><code>chore:
lint</code></a></li>
<li><a
href="35a6a61860"><code>chore:
update deps</code></a></li>
<li><a
href="b396aec458"><code>chore:
add defaults in JSDocs (#365)</code></a></li>
<li><a
href="aa541a64ae"><code>chore(deps):
update autofix-ci/action digest to 551dded (#358)</code></a></li>
<li><a
href="fb2b903cc2"><code>chore:
update deps</code></a></li>
<li><a
href="c7cfeedbd2"><code>test:
update snapshot</code></a></li>
<li><a
href="6b7fe8b7e6"><code>chore:
update ci</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-31 11:48:58 -04:00
Jordan Pittman
68a79b159c
Suggest bare values for flex-* utilities (#18642)
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1426

We weren't suggesting things like `flex-1`. This adds that. I'm not sure
if we want to suggest more `flex-<number>` values though. I've added
1–12 but am open to changing this.
2025-07-30 10:48:26 -04:00
Jordan Pittman
492304212f
Allow users to disable url rewriting in the PostCSS plugin (#18321)
Since we (optionally) support source maps now it's possible that a later
PostCSS plugin that *still* does url rewriting might fail to do so
correctly because nodes will have preserved source locations in dev
builds where before we "pretended" that everything came from the
original file.

But, because we can't know if such a plugin is present, disabling this
behavior when source maps are enabled could cause issues *and* would be
a breaking change.

I wish everything *could just work* here but realistically we can't know
what plugins have run before our PostCSS plugin or what plugins will run
after so the best option (I think) we can offer here is to allow users
to disable url rewriting at the plugin level.

Fixes #16700
2025-07-30 10:35:10 -04:00
depfu[bot]
afbfdebf06
Update all of nextjs 15.3.4 → 15.4.4 (minor) (#18636)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint-config-next (15.3.4 → 15.4.4)





Sorry, we couldn't find anything useful about this release.



#### ✳️ next (15.3.4 → 15.4.4) ·
[Repo](https://github.com/vercel/next.js)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.4.4">15.4.4</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.4.3">15.4.3</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.4.2">15.4.2</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.4.1">15.4.1</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.3.5">15.3.5</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="f98040047e...fe5db65859">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-30 10:32:16 -04:00
depfu[bot]
b89d9c3702
Update prettier-plugin-organize-imports 4.0.0 → 4.2.0 (minor) (#18634)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ prettier-plugin-organize-imports (4.0.0 → 4.2.0) ·
[Repo](https://github.com/simonhaenisch/prettier-plugin-organize-imports)
·
[Changelog](https://github.com/simonhaenisch/prettier-plugin-organize-imports/blob/master/changelog.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/simonhaenisch/prettier-plugin-organize-imports/releases/tag/v4.1.0">4.1.0</a></h4>

<blockquote><p dir="auto">Bumped the peer dependency range for <code
class="notranslate">vue-tsc</code> to <code
class="notranslate">^2.1.0</code> because there was a breaking change in
its API. If you're using Vue support, upgrade both packages
simultaneously, e.g. <code class="notranslate">npm i -D
prettier-plugin-organize-imports vue-tsc</code>.</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="8ae2dddcc5...93df5019c4">See
the full diff on Github</a>. The new version differs by 14 commits:</p>
<ul>
<li><a
href="93df5019c4"><code>4.2.0</code></a></li>
<li><a
href="253d1bffcb"><code>chore:
update all dev dependencies</code></a></li>
<li><a
href="858cbbea84"><code>fix:
use `getDefaultCompilerOptions` from vue-tsc instead of the deprecated
`resolveVueCompilerOptions`</code></a></li>
<li><a
href="c777cd8c2a"><code>feat:
allow vue-tsc 3 as peer dependency</code></a></li>
<li><a
href="e79ed64e5e"><code>chore(ci):
remove `check-types` step (part of `test` now)</code></a></li>
<li><a
href="b7c15ad9f5"><code>4.1.0</code></a></li>
<li><a
href="2e9c480b72"><code>chore:
update dev dependencies</code></a></li>
<li><a
href="fb7e872d64"><code>docs:
update changelog</code></a></li>
<li><a
href="3fb452e7df"><code>feat:
support vue-tsc v2.1 (#136)</code></a></li>
<li><a
href="4678129bd0"><code>chore(ci):
run on node 22</code></a></li>
<li><a
href="fa997242e4"><code>chore(ci):
fix run-script invocation</code></a></li>
<li><a
href="3ea4a8151f"><code>chore:
use tsc to type-check the project</code></a></li>
<li><a
href="59c5329c46"><code>chore(tests):
upgrade ava from v3 to v6</code></a></li>
<li><a
href="b253038883"><code>fix:
drop `@vue/language-plugin-pug` as peer dependency</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-29 14:01:37 -04:00
depfu[bot]
e73637d372
Update @playwright/test 1.53.2 → 1.54.1 (minor) (#18631)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​playwright/test (1.53.2 → 1.54.1) ·
[Repo](https://github.com/Microsoft/playwright)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/microsoft/playwright/releases/tag/v1.54.1">1.54.1</a></h4>

<blockquote><h3 dir="auto">Highlights</h3>
<p dir="auto"><a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/36650">#36650</a>
- [Regression]: 1.54.0 breaks downloading browsers when an HTTP(S) proxy
is used</p>
<h2 dir="auto">Browser Versions</h2>
<ul dir="auto">
<li>Chromium 139.0.7258.5</li>
<li>Mozilla Firefox 140.0.2</li>
<li>WebKit 26.0</li>
</ul>
<p dir="auto">This version was also tested against the following stable
channels:</p>
<ul dir="auto">
<li>Google Chrome 140</li>
<li>Microsoft Edge 140</li>
</ul></blockquote>
<h4><a
href="https://github.com/microsoft/playwright/releases/tag/v1.54.0">1.54.0</a></h4>

<blockquote><h2 dir="auto">Highlights</h2>
<ul dir="auto">
<li>
<p dir="auto">New cookie property <code
class="notranslate">partitionKey</code> in <a
href="https://playwright.dev/docs/api/class-browsercontext#browser-context-cookies">browserContext.cookies()</a>
and <a
href="https://playwright.dev/docs/api/class-browsercontext#browser-context-add-cookies">browserContext.addCookies()</a>.
This property allows to save and restore partitioned cookies. See <a
href="https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Privacy_sandbox/Partitioned_cookies">CHIPS
MDN article</a> for more information. Note that browsers have different
support and defaults for cookie partitioning.</p>
</li>
<li>
<p dir="auto">New option <code class="notranslate">noSnippets</code> to
disable code snippets in the html report.</p>
<div class="highlight highlight-source-js" dir="auto"><pre
class="notranslate"><span class="pl-k">import</span> <span
class="pl-kos">{</span> <span class="pl-s1">defineConfig</span> <span
class="pl-kos">}</span> <span class="pl-k">from</span> <span
class="pl-s">'@playwright/test'</span><span class="pl-kos">;</span>

<span class="pl-k">export</span> <span class="pl-k">default</span> <span
class="pl-en">defineConfig</span><span class="pl-kos">(</span><span
class="pl-kos">{</span>
<span class="pl-c1">reporter</span>: <span class="pl-kos">[</span><span
class="pl-kos">[</span><span class="pl-s">'html'</span><span
class="pl-kos">,</span> <span class="pl-kos">{</span> <span
class="pl-c1">noSnippets</span>: <span class="pl-c1">true</span> <span
class="pl-kos">}</span><span class="pl-kos">]</span><span
class="pl-kos">]</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span
class="pl-kos">;</span></pre></div>
</li>
<li>
<p dir="auto">New property <code class="notranslate">location</code> in
test annotations, for example in <a
href="https://playwright.dev/docs/api/class-testresult#test-result-annotations">testResult.annotations</a>
and <a
href="https://playwright.dev/docs/api/class-testinfo#test-info-annotations">testInfo.annotations</a>.
It shows where the annotation like <code
class="notranslate">test.skip</code> or <code
class="notranslate">test.fixme</code> was added.</p>
</li>
</ul>
<h2 dir="auto">Command Line</h2>
<ul dir="auto">
<li>
<p dir="auto">New option <code
class="notranslate">--user-data-dir</code> in multiple commands. You can
specify the same user data dir to reuse browsing state, like
authentication, between sessions.</p>
<div class="highlight highlight-source-shell" dir="auto"><pre
class="notranslate">npx playwright codegen
--user-data-dir=./user-data</pre></div>
</li>
<li>
<p dir="auto">Option <code class="notranslate">-gv</code> has been
removed from the <code class="notranslate">npx playwright test</code>
command. Use <code class="notranslate">--grep-invert</code> instead.</p>
</li>
<li>
<p dir="auto"><code class="notranslate">npx playwright open</code> does
not open the test recorder anymore. Use <code class="notranslate">npx
playwright codegen</code> instead.</p>
</li>
</ul>
<h2 dir="auto">Miscellaneous</h2>
<ul dir="auto">
<li>Support for Node.js 16 has been removed.</li>
<li>Support for Node.js 18 has been deprecated, and will be removed in
the future.</li>
</ul>
<h2 dir="auto">Browser Versions</h2>
<ul dir="auto">
<li>Chromium 139.0.7258.5</li>
<li>Mozilla Firefox 140.0.2</li>
<li>WebKit 26.0</li>
</ul>
<p dir="auto">This version was also tested against the following stable
channels:</p>
<ul dir="auto">
<li>Google Chrome 140</li>
<li>Microsoft Edge 140</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="8c38de4d13...97b6b881b7">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-28 16:29:06 -04:00
Jordan Pittman
06007b1eec
Fix Windows CI failures (#18611)
Seems like updating Turbo caused bun to break but only on Windows. No
idea why.
2025-07-25 07:28:50 -04:00
depfu[bot]
67022aa5f4
Update @vitejs/plugin-react 4.6.0 → 4.7.0 (minor) (#18608)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (4.6.0 → 4.7.0) ·
[Repo](https://github.com/vitejs/vite) ·
[Changelog](https://github.com/vitejs/vite/blob/main/packages/plugin-react/CHANGELOG.md)
















---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-25 07:13:25 -04:00
depfu[bot]
db2770a3f6
Update @emnapi/wasi-threads 1.0.3 → 1.0.4 (patch) (#18609)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​emnapi/wasi-threads (1.0.3 → 1.0.4) ·
[Repo](https://github.com/toyobayashi/emnapi)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-25 07:07:46 -04:00
depfu[bot]
6314c8fb36
Update turbo 2.5.4 → 2.5.5 (patch) (#18595)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ turbo (2.5.4 → 2.5.5) ·
[Repo](https://github.com/turborepo/turbo)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-23 20:22:41 -04:00
Jordan Pittman
fa3f45f02c
Don’t output CSS objects with false or undefined in the AST (#18571)
Fixes https://github.com/tailwindlabs/tailwindcss-typography/issues/384

Basically when addUtilities/addComponents/matchUtilities/matchComponents
saw a value of `false` it was being output instead of being discarded
like it was in v3.

The types really require these to be strings but for things like the
typography plugin this isn't really carried through from its theme
config so it was easy to put anything in there and not realize it
doesn't match the expected types.

Basically this:
```js
addUtilities({
  '.foo': {
    a: 'red',
    'z-index': 0,
    '.bar': false,
    '.baz': null, // this one already worked
    '.qux': undefined,
  },
})
```

Now works like it did in v3 and omits `.bar`, `.baz`, and `.qux`
2025-07-21 15:39:34 -04:00
depfu[bot]
847ed1e4d7 Update @types/bun to version 1.2.18 2025-07-21 14:20:28 +00:00
depfu[bot]
203073f0b8
Update bun 1.2.17 → 1.2.18 (patch) (#18574)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ bun (1.2.17 → 1.2.18) · [Repo](https://github.com/oven-sh/bun)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-21 10:14:43 -04:00
Jordan Pittman
939fda6f4e
Show more specific error for functional-like but invalid utility names (#18568)
Fixes #18566

The behavior is by design but the error message could definitely be
improved.
2025-07-18 12:31:19 -04:00
Hugo Parente Lima
298ad08653
Add support for Slang template (#18565)
## Summary

Slang is basically a Slim template language for Crystal language, so the
very same Slim parser works fine.

Slim template: https://github.com/slim-template/slim
Slang template: https://github.com/jeromegn/slang

## Test plan

Create a simple slang file with some tailwind-css and check if the CSS
is being extracted:
```slim
doctype html
html
  head
    title This is a title
  body.min-h-screen
    header.stick.top-0.z-10
      section.max-w-4xl.mx-auto.p-4.flex.items-center.justify-between
        h1.text-3xl.font-medium This is a slang file
``` 
To test it, get any slim template, rename the extension to .slang 

Fixes #17851

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-07-17 14:10:19 -04:00
depfu[bot]
03aff9e613
Update @napi-rs/wasm-runtime 0.2.11 → 0.2.12 (minor) (#18562)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​napi-rs/wasm-runtime (0.2.11 → 0.2.12) ·
[Repo](https://github.com/napi-rs/napi-rs)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-17 11:00:59 -04:00
Jordan Pittman
88b9f15b65
Center the dropdown icon added to an input with a paired datalist in Chrome (#18511)
This PR tweaks the dropdown arrow added to an input by Chrome when it
has a `list` attribute pointing to a `<datalist>`.

Right now the arrow isn't centered vertically:

<img width="227" height="58" alt="Screenshot 2025-07-14 at 15 41 50"
src="https://github.com/user-attachments/assets/b354a5e8-432d-432d-bfe4-f7b6f6683548"
/>

The cause of this is the line height being inherited into the pseudo
element which controls how the marker is positioned. I *think* this is
because it's being drawn with unicode symbols but I'm not sure. It could
just be from the `list-item` display.

After this PR changes the line height its centered again:

<img width="227" height="58" alt="Screenshot 2025-07-14 at 15 42 05"
src="https://github.com/user-attachments/assets/1afa1f33-cc28-4b1f-9e04-e546f6848f57"
/>

Some notes:

This only affects Chrome and also does not appear to cause issues for
date/time inputs. While weird that this pseudo is the one used for a
`<datalist>` marker it is indeed correct.

Fixes #18499

Can use this Play to test the change:
https://play.tailwindcss.com/jzT35CRpr0

---------

Co-authored-by: Jonathan Reinink <jonathan@reinink.ca>
2025-07-15 14:41:04 -04:00
depfu[bot]
ce0e775212
Update @tybys/wasm-util 0.9.0 → 0.10.0 (major) (#18541)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​tybys/wasm-util (0.9.0 → 0.10.0) ·
[Repo](https://github.com/toyobayashi/wasm-util)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-15 10:14:57 -04:00
depfu[bot]
2a4b0b59fd
Update @emnapi/core 1.4.3 → 1.4.4 (patch) (#18535)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​emnapi/core (1.4.3 → 1.4.4) ·
[Repo](https://github.com/toyobayashi/emnapi)




<details>
<summary>Commits</summary>
<p><a
href="90c4c1b135...87bb86c3a5">See
the full diff on Github</a>. The new version differs by 2 commits:</p>
<ul>
<li><a
href="87bb86c3a5"><code>1.4.4</code></a></li>
<li><a
href="92d5bba8c7"><code>fix:
`worker.onerror` may receive an `Event`</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-14 11:59:50 -04:00
depfu[bot]
9cfd20b19b
Update emnapi 1.4.3 → 1.4.4 (patch) (#18534)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ emnapi (1.4.3 → 1.4.4) ·
[Repo](https://github.com/toyobayashi/emnapi)




<details>
<summary>Commits</summary>
<p><a
href="90c4c1b135...87bb86c3a5">See
the full diff on Github</a>. The new version differs by 2 commits:</p>
<ul>
<li><a
href="87bb86c3a5"><code>1.4.4</code></a></li>
<li><a
href="92d5bba8c7"><code>fix:
`worker.onerror` may receive an `Event`</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-14 11:59:39 -04:00
Jordan Pittman
798a7bf905
Ignore consecutive semicolons in the CSS parser (#18532)
Fixes #18523

I swear I'd already landed this but apparently not.
2025-07-14 11:44:31 -04:00
depfu[bot]
e0eac192ff Update @emnapi/runtime to version 1.4.4 2025-07-14 15:11:29 +00:00
depfu[bot]
cbe06694cb
Update @emnapi/wasi-threads 1.0.2 → 1.0.3 (patch) (#18531)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​emnapi/wasi-threads (1.0.2 → 1.0.3) ·
[Repo](https://github.com/toyobayashi/emnapi)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-14 11:08:42 -04:00
depfu[bot]
d56564152e
Update eslint 9.29.0 → 9.30.1 (minor) (#18513)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.29.0 → 9.30.1) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.30.1">9.30.1</a></h4>

<blockquote><h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="e91bb870f8"><code
class="notranslate">e91bb87</code></a> fix: allow separate default and
named type imports (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19899">#19899</a>)
(xbinaryx)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="ab7c62598a"><code
class="notranslate">ab7c625</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="dae1e5bb27"><code
class="notranslate">dae1e5b</code></a> docs: update jsdoc's link (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19896">#19896</a>)
(JamesVanWaza)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="b035f747c6"><code
class="notranslate">b035f74</code></a> chore: upgrade to <code
class="notranslate">@eslint/js@9.30.1</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19906">#19906</a>)
(Francesco Trotta)</li>
<li>
<a
href="b3dbc16563"><code
class="notranslate">b3dbc16</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
</ul></blockquote>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.30.0">9.30.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="52a5fcaa4e"><code
class="notranslate">52a5fca</code></a> feat: Support <code
class="notranslate">basePath</code> property in config objects (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19879">#19879</a>)
(Milos Djermanovic)</li>
<li>
<a
href="4ab44823df"><code
class="notranslate">4ab4482</code></a> feat: add <code
class="notranslate">allowSeparateTypeImports</code> option to <code
class="notranslate">no-duplicate-imports</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19872">#19872</a>)
(sethamus)</li>
<li>
<a
href="b8a7e7aeb5"><code
class="notranslate">b8a7e7a</code></a> feat: throw error when column is
negative in <code class="notranslate">getIndexFromLoc</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19831">#19831</a>)
(루밀LuMir)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="6a0f164543"><code
class="notranslate">6a0f164</code></a> fix: handle <code
class="notranslate">null</code> type <code
class="notranslate">loc</code> in <code
class="notranslate">getIndexFromLoc</code> method (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19862">#19862</a>)
(루밀LuMir)</li>
<li>
<a
href="3fbcd704a0"><code
class="notranslate">3fbcd70</code></a> fix: update error message for
<code class="notranslate">no-restricted-properties</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19855">#19855</a>)
(Tanuj Kanti)</li>
<li>
<a
href="7ef4cf7661"><code
class="notranslate">7ef4cf7</code></a> fix: remove unnecessary semicolon
from fixes (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19857">#19857</a>)
(Francesco Trotta)</li>
<li>
<a
href="7dabc38a84"><code
class="notranslate">7dabc38</code></a> fix: use <code
class="notranslate">process.version</code> in <code
class="notranslate">--env-info</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19865">#19865</a>)
(TKDev7)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="8662ed1f6d"><code
class="notranslate">8662ed1</code></a> docs: adopt eslint-stylistic sub
packages related changes (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19887">#19887</a>)
(ntnyq)</li>
<li>
<a
href="20158b09db"><code
class="notranslate">20158b0</code></a> docs: typo in comment for unused
variables handling (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19870">#19870</a>)
(leopardracer)</li>
<li>
<a
href="ebfb5b4613"><code
class="notranslate">ebfb5b4</code></a> docs: Fixed Typo in
configuration-files.md (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19873">#19873</a>)
(0-20)</li>
<li>
<a
href="4112fd0953"><code
class="notranslate">4112fd0</code></a> docs: clarify that boolean is
still allowed for rule <code class="notranslate">meta.deprecated</code>
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19866">#19866</a>)
(Bryan Mishkin)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="2b6491cd4b"><code
class="notranslate">2b6491c</code></a> chore: upgrade to <code
class="notranslate">@eslint/js@9.30.0</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19889">#19889</a>)
(Francesco Trotta)</li>
<li>
<a
href="5a5d526103"><code
class="notranslate">5a5d526</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="eaf8a418af"><code
class="notranslate">eaf8a41</code></a> chore: Correct typos in linter
tests (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19878">#19878</a>)
(kilavvy)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="edf232b680...6769b5fa11">See
the full diff on Github</a>. The new version differs by 23 commits:</p>
<ul>
<li><a
href="6769b5fa11"><code>9.30.1</code></a></li>
<li><a
href="b2950ace77"><code>Build:
changelog update for 9.30.1</code></a></li>
<li><a
href="b035f747c6"><code>chore:
upgrade to `@eslint/js@9.30.1` (#19906)</code></a></li>
<li><a
href="b3dbc16563"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="e91bb870f8"><code>fix:
allow separate default and named type imports (#19899)</code></a></li>
<li><a
href="ab7c62598a"><code>docs:
Update README</code></a></li>
<li><a
href="dae1e5bb27"><code>docs:
update jsdoc&#39;s link (#19896)</code></a></li>
<li><a
href="ad1d639517"><code>9.30.0</code></a></li>
<li><a
href="c02d70b718"><code>Build:
changelog update for 9.30.0</code></a></li>
<li><a
href="2b6491cd4b"><code>chore:
upgrade to `@eslint/js@9.30.0` (#19889)</code></a></li>
<li><a
href="5a5d526103"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="52a5fcaa4e"><code>feat:
Support `basePath` property in config objects (#19879)</code></a></li>
<li><a
href="6a0f164543"><code>fix:
handle `null` type `loc` in `getIndexFromLoc` method
(#19862)</code></a></li>
<li><a
href="8662ed1f6d"><code>docs:
adopt eslint-stylistic sub packages related changes
(#19887)</code></a></li>
<li><a
href="eaf8a418af"><code>chore:
Correct typos in linter tests (#19878)</code></a></li>
<li><a
href="4ab44823df"><code>feat:
add `allowSeparateTypeImports` option to `no-duplicate-imports`
(#19872)</code></a></li>
<li><a
href="3fbcd704a0"><code>fix:
update error message for `no-restricted-properties`
(#19855)</code></a></li>
<li><a
href="20158b09db"><code>docs:
typo in comment for unused variables handling (#19870)</code></a></li>
<li><a
href="ebfb5b4613"><code>docs:
Fixed Typo in configuration-files.md (#19873)</code></a></li>
<li><a
href="b8a7e7aeb5"><code>feat:
throw error when column is negative in `getIndexFromLoc`
(#19831)</code></a></li>
<li><a
href="7ef4cf7661"><code>fix:
remove unnecessary semicolon from fixes (#19857)</code></a></li>
<li><a
href="7dabc38a84"><code>fix:
use `process.version` in `--env-info` (#19865)</code></a></li>
<li><a
href="4112fd0953"><code>docs:
clarify that boolean is still allowed for rule `meta.deprecated`
(#19866)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-14 10:06:09 -04:00
Jelf
fac8cd9def
fix: allow process.env.DEBUG to be a boolean in @tailwindcss/node (#18485)
TanStack Start build to `cloudflare-module`, `debug` value type is
boolean.
reference:
https://developers.cloudflare.com/workers/framework-guides/web-apps/tanstack/

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-07-11 11:51:50 -04:00
depfu[bot]
c3e2335308
Update @playwright/test 1.53.0 → 1.53.2 (patch) (#18487)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​playwright/test (1.53.0 → 1.53.2) ·
[Repo](https://github.com/Microsoft/playwright)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/microsoft/playwright/releases/tag/v1.53.2">1.53.2</a></h4>

<blockquote><h3 dir="auto">Highlights</h3>
<p dir="auto"><a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/36317">#36317</a>
- [Regression]: Merging pre-1.53 blob reports loses attachments<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/pull/36357">#36357</a>
- [Regression (Chromium)]: CDP missing trailing slash<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/36292">#36292</a>
- [Bug (MSEdge)]: Edge fails to launch when using <code
class="notranslate">msRelaunchNoCompatLayer</code></p>
<h2 dir="auto">Browser Versions</h2>
<ul dir="auto">
<li>Chromium 138.0.7204.23</li>
<li>Mozilla Firefox 139.0</li>
<li>WebKit 18.5</li>
</ul>
<p dir="auto">This version was also tested against the following stable
channels:</p>
<ul dir="auto">
<li>Google Chrome 137</li>
<li>Microsoft Edge 137</li>
</ul></blockquote>
<h4><a
href="https://github.com/microsoft/playwright/releases/tag/v1.53.1">1.53.1</a></h4>

<blockquote><h3 dir="auto">Highlights</h3>
<p dir="auto"><a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/36339">#36339</a>
- [Regression]: Click can fail when scrolling required<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/36307">#36307</a>
- [Regression (Chromium)]: Under some scenarios filling a <code
class="notranslate">textarea</code> doesn't fill<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/36294">#36294</a>
- [Regression (Firefox)]: <code
class="notranslate">setViewportSize</code> times out<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/pull/36350">#36350</a>
- [Fix]: Display HTTP method for fetch trace entries</p>
<h2 dir="auto">Browser Versions</h2>
<ul dir="auto">
<li>Chromium 138.0.7204.23</li>
<li>Mozilla Firefox 139.0</li>
<li>WebKit 18.5</li>
</ul>
<p dir="auto">This version was also tested against the following stable
channels:</p>
<ul dir="auto">
<li>Google Chrome 137</li>
<li>Microsoft Edge 137</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="682277722c...8c38de4d13">See
the full diff on Github</a>. The new version differs by 10 commits:</p>
<ul>
<li><a
href="8c38de4d13"><code>chore:
mark v1.53.2 (#36502)</code></a></li>
<li><a
href="50d76d7910"><code>(#36462):
fix(chromium): fix compatibility with Edge msRelaunchNoCompatLayer
feature</code></a></li>
<li><a
href="48be646aa4"><code>cherry-pick(#36443):
fix(blob): correctly type pre-1.53 onTestEnd event for attachments
(#36476)</code></a></li>
<li><a
href="dc1555648b"><code>cherry-pick(#36377):
chore: follow-up to connectOverCDP fetch logic</code></a></li>
<li><a
href="4d0938cb2e"><code>cherry-pick(#36357):
fix: adding trialing slash detection logic back in
urlToWSEndpoint</code></a></li>
<li><a
href="3c60cb4766"><code>chore:
mark v1.53.1 (#36355)</code></a></li>
<li><a
href="b7032c23bf"><code>cherry-pick(#36350):
fix(trace): include method into &quot;Fetch&quot; action title
(#36352)</code></a></li>
<li><a
href="e0d1741710"><code>cherry-pick(#36346):
Revert &quot;chore: reduce scrolling during clicks
(#36175)&quot;</code></a></li>
<li><a
href="c568980885"><code>cherry-pick(#36340):
feat(firefox): roll to r1488</code></a></li>
<li><a
href="e773cddd24"><code>cherry-pick(#36301):
feat(chromium): roll to r1179 (#36301)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-08 16:21:02 -04:00
depfu[bot]
bd9cf929d4
Update @vitejs/plugin-react 4.5.2 → 4.6.0 (minor) (#18477)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (4.5.2 → 4.6.0) ·
[Repo](https://github.com/vitejs/vite) ·
[Changelog](https://github.com/vitejs/vite/blob/main/packages/plugin-react/CHANGELOG.md)
















---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-08 08:17:58 -04:00
Jordan Pittman
2941a7b5c2
Track source locations through @plugin and @config (#18329)
Fixes https://github.com/tailwindlabs/tailwindcss-forms/issues/182

Inside JS plugins and configs we weren't tracking source location info
so using things like `addBase(…)` could result in warnings inside Vite's
url rewriting PostCSS plugin because not all declarations had a source
location.

The goal here is for calls to `addBase` to generate CSS that points back
to the `@plugin` or `@config` that resulted in it being called.
2025-07-03 11:20:54 -04:00
Robin Malfait
9169d73aad
update READMEs
Co-Authored-By: Adam Wathan <adam.wathan@gmail.com>
Co-Authored-By: Jonathan Reinink <jonathan@reinink.ca>
2025-07-02 22:49:47 +02:00
Adam Wathan
d2fdddf8d3
Update README.md (#18434)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

## Summary

<!--

Provide a summary of the issue and the changes you're making. How does
your change solve the problem?

-->

## Test plan

<!--

Explain how you tested your changes. Include the exact commands that you
used to verify the change works and include screenshots/screen
recordings of the update behavior in the browser if applicable.

-->
2025-07-02 16:40:30 -04:00
Jordan Pittman
8c8033017d
Detect classes inside Elixir charlist, word list, and string sigils (#18432)
Fixes #18431

The `~W(…)` syntax in Elixir is a _sigil_. It's basically another way to
write strings, arrays of strings, etc… The syntax lets you use a handful
of surrounding brackets like `~W(…)`, `~W[…]`, `~W{…}`, `~W"…"`, etc… to
let you write lists without necessarily having to escape characters.

In v3 our extractor was able to pick these up but in v4 Oxide does not.
I've added a preprocessor for Elixir files so we can modify the code
before our main extractor sees it.

Now things like this: `~W(text-white bg-gray-600)` will get turned into
` ~W text-white bg-gray-600 ` which can easily be processed by our
extractor.

The sigils we support are:
- `~s` / `~S` (strings)
- `~w` / `~W` (word lists)
- `~c` / `~C` (charlists)

We're specifically detecting the use of `(…)`, `[…]`, and `{…}` as using
quotes already works today.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-07-02 16:01:13 -04:00
Jonathan Reinink
3cab801761
Create FUNDING.yml 2025-07-02 13:22:16 -04:00
depfu[bot]
f307c31d45
Update enhanced-resolve 5.18.1 → 5.18.2 (patch) (#18423)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ enhanced-resolve (5.18.1 → 5.18.2) ·
[Repo](https://github.com/webpack/enhanced-resolve)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/webpack/enhanced-resolve/releases/tag/v5.18.2">5.18.2</a></h4>

<blockquote><h3 dir="auto">Fixes</h3>
<ul dir="auto">
<li>[Types] FileSystem type</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="9436f4d6d9...0bf45033f4">See
the full diff on Github</a>. The new version differs by 14 commits:</p>
<ul>
<li><a
href="0bf45033f4"><code>chore(release):
5.18.2</code></a></li>
<li><a
href="b2441769bd"><code>fix:
types</code></a></li>
<li><a
href="775f2fb8ed"><code>chore:
migrate to eslint-config-webpack (#453)</code></a></li>
<li><a
href="6df312e9a6"><code>chore:
fix tsconfig (#452)</code></a></li>
<li><a
href="b059bff8ce"><code>ci:
show report</code></a></li>
<li><a
href="c974464f46"><code>chore:
fix</code></a></li>
<li><a
href="29f9405129"><code>chore:
fix small stuff</code></a></li>
<li><a
href="01a04fd898"><code>chore:
refactor dev env</code></a></li>
<li><a
href="66a745681a"><code>ci:
show report</code></a></li>
<li><a
href="3bf44c7a6e"><code>ci:
node v24</code></a></li>
<li><a
href="bbbf6ab5b0"><code>ci:
node v24</code></a></li>
<li><a
href="38e9fd9acb"><code>feat:
export `SyncFileSystem` and `BaseFileSystem` types</code></a></li>
<li><a
href="3c9d4b6d51"><code>chore:
fix generation</code></a></li>
<li><a
href="4918c5b3a4"><code>feat:
export type SyncFileSystem and type BaseFileSystem</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-07-01 09:23:44 -04:00
Jordan Pittman
98158fd372 Fix failing test 2025-06-30 12:23:56 -04:00
Henrik Eneroth
05b65d59b5
Fix trailing ) from interfering with extraction in Clojure keywords (#18345)
## Summary

In a form like, 

```clojure 
(if condition :bg-white :bg-black)
```

`:bg-black` will fail to extract, while `:bg-white` is extracted as
expected. This PR fixes this case, implements more comprehensive
candidate filtering, and supersedes a previous PR.

Having recently submitted a PR for handling another special case with
Clojure keywords (the presence of `:` inside of keywords), I thought it
best to invert the previous strategy: Instead of handling special cases
one by one, consume keywords according to the Clojure reader spec.
Consume nothing else, other than strings.

Because of this, this PR is a tad more invasive rather than additive,
for which I apologize. The strategy is this:
- Strings begin with a `"` and ends with an unescaped `"`. Consume
everything between these delimiters (existing case).
- Keywords begin with `:`, and end with whitespace, or one out of a
small set of specific reserved characters. Everything else is a valid
character in a keyword. Consume everything between these delimiters, and
apply the class splitting previously contained in the outer loop. My
previous special case handling of `:` inside of keywords in #18338 is
now redundant (and is removed), as this is a more general solution.
- Discard _everything else_. 

I'm hoping that a strategy that is based on Clojure's definition of
strings and keywords will pre-empt any further issues with edge cases.

Closes #18344.

## Test plan
- Added failing tests.
- `cargo test` -> failure
- Added fix
- `cargo test` -> success

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-06-30 12:23:38 -04:00
depfu[bot]
7946db05ef
Update @types/bun 1.2.15 → 1.2.17 (patch) (#18409)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/bun (1.2.15 → 1.2.17) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-30 13:40:00 +00:00
depfu[bot]
ca7caae502
Update bun 1.2.15 → 1.2.17 (patch) (#18408)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ bun (1.2.15 → 1.2.17) · [Repo](https://github.com/oven-sh/bun)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-30 09:31:26 -04:00
Jordan Pittman
2030e942bc
Don't consider the global important state in @apply (#18404)
Fixes #18400

In v3 when you used `important: true` it did not affect `@apply`.
However, in v4 it does and there's no way to make it *not*. This is
definitely a bug and would be unexpected for users coming from v3 who
use `@apply` and `important` together.

Basically, the following code, along with the detected utility `flex` in
source files…

```css
@import 'tailwindcss/utilities' important;
.flex-explicitly-important {
  @apply flex!;
}
.flex-not-important {
  @apply flex;
}
```

… would output this:
```css
.flex {
  display: flex !important;
}
.flex-explicitly-important  {
  display: flex !important;
}
.flex-not-important {
  display: flex !important;
}
```

But it's expected that `@apply` doesn't consider the "global" important
state. This PR addresss this problem and now the output is this:

```css
.flex {
  display: flex !important;
}
.flex-explicitly-important  {
  display: flex !important;
}
.flex-not-important {
  display: flex; /* this line changed */
}
```

If you want to mark a utility as important in `@apply` you can still use
`!` after the utility to do so as shown above.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-06-27 11:55:15 -04:00
depfu[bot]
b716d10129
Update all of nextjs 15.3.3 → 15.3.4 (patch) (#18401)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint-config-next (15.3.3 → 15.3.4)





Sorry, we couldn't find anything useful about this release.



#### ✳️ next (15.3.3 → 15.3.4) ·
[Repo](https://github.com/vercel/next.js)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.3.4">15.3.4</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon
octicon-info mr-2" viewbox="0 0 16 16" version="1.1" width="16"
height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0
8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1
7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1
0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0
2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>[metadata] render streaming metadata on the top level (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/80566">#80566</a>)</li>
<li>[fix] clone the config module to avoid mutation (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/80573">#80573</a>)</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to <a
href="https://bounce.depfu.com/github.com/huozhi">@huozhi</a> for
helping!</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>


Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-26 15:44:28 -04:00
Robin Malfait
b24457a9f4
Prepare 4.1.11 (#18397) 2025-06-26 12:43:17 +02:00
depfu[bot]
6ad26de66b
Update postcss 8.5.4 → 8.5.6 (patch) (#18395)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ postcss (8.5.4 → 8.5.6) ·
[Repo](https://github.com/postcss/postcss) ·
[Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/postcss/postcss/releases/tag/8.5.6">8.5.6</a></h4>

<blockquote><ul dir="auto">
<li>Fixed <code class="notranslate">ContainerWithChildren</code> type
discriminating (by <a
href="https://bounce.depfu.com/github.com/Goodwine">@Goodwine</a>).</li>
</ul></blockquote>
<h4><a
href="https://github.com/postcss/postcss/releases/tag/8.5.5">8.5.5</a></h4>

<blockquote><ul dir="auto">
<li>Fixed <code class="notranslate">package.json</code>→<code
class="notranslate">exports</code> compatibility with some tools (by <a
href="https://bounce.depfu.com/github.com/JounQin">@JounQin</a>).</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="6cb4a6673f...91d6eb5c3d">See
the full diff on Github</a>. The new version differs by 7 commits:</p>
<ul>
<li><a
href="91d6eb5c3d"><code>Release
8.5.6 version</code></a></li>
<li><a
href="65ffc55117"><code>Update
dependencies</code></a></li>
<li><a
href="ecd20eb7f9"><code>Fix
ContainerWithChildren to allow discriminating the node type by comparing
its type property (#2049)</code></a></li>
<li><a
href="c18159719e"><code>Release
8.5.5 version</code></a></li>
<li><a
href="c5523fbec5"><code>Update
dependencies</code></a></li>
<li><a
href="2e3450c55f"><code>refactor:
`import` should be listed before `require` (#2052)</code></a></li>
<li><a
href="4d720bd01a"><code>Update
EM text</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-26 11:28:01 +02:00
Rózsa Zoltán
aa859314d9
feat: add Vite 7 support to the @tailwindcss/vite plugin (#18384)
Closes #18381 

* [Changelog for Vite 7.0.0
(2025-06-24)](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md#700-2025-06-24)

Starting from Vite 7, Node 18 support will be dropped, which doesn't
really affect Tailwind. It might be worth mentioning in the
documentation that the recommended minimum Node versions are 20.19 and
22.12.

Vite 7 is only available in ESM format, which is also not an issue.

Vite's browser support aligns with the v4 guidelines:
```
Chrome 87 → 107       (tw: 111)
Edge 88 → 107         (tw: 111)
Firefox 78 → 104      (tw: 128)
Safari 14.0 → 16.0    (tw: 16.4)
```
* [Vite 7 - Browser
Support](https://vite.dev/guide/migration.html#default-browser-target-change)
* [Tailwind CSS v4 - Browser
Support](https://tailwindcss.com/docs/compatibility#browser-support)

So, at first glance, there's nothing more to do except enabling support
for these versions.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-06-24 12:31:17 -04:00
depfu[bot]
b9007dd9f8
Update @types/react 19.1.6 → 19.1.8 (patch) (#18378)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react (19.1.6 → 19.1.8) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-24 09:57:55 -04:00
depfu[bot]
767a60a195
Update postcss-import 16.1.0 → 16.1.1 (patch) (#18376)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ postcss-import (16.1.0 → 16.1.1) ·
[Repo](https://github.com/postcss/postcss-import) ·
[Changelog](https://github.com/postcss/postcss-import/blob/master/CHANGELOG.md)



<details>
<summary>Release Notes</summary>

<h4>16.1.1 (from changelog)</h4>
<blockquote><ul dir="auto">
<li>Fix incorrect cascade layer order when some resources can not be
inlined (<a
href="https://bounce.depfu.com/github.com/postcss/postcss-import/issues/567">#567</a>,
<a
href="https://bounce.depfu.com/github.com/postcss/postcss-import/pull/574">#574</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let
us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="9217ec361b...4ae9894edc">See
the full diff on Github</a>. The new version differs by 10 commits:</p>
<ul>
<li><a
href="4ae9894edc"><code>16.1.1</code></a></li>
<li><a
href="a3f38897da"><code>Test
on modern Node versions (#577)</code></a></li>
<li><a
href="10325fc024"><code>Upgrade
eslint &amp; config; use flat config (#576)</code></a></li>
<li><a
href="9227642040"><code>Migrate
config renovate.json (#575)</code></a></li>
<li><a
href="2544155491"><code>Update
dependency prettier to ~3.5.0 (#572)</code></a></li>
<li><a
href="83108aa207"><code>Fix
incorrect cascade layer order when some resources can not be inlined
(#574)</code></a></li>
<li><a
href="cad00220bf"><code>Update
dependency sugarss to v5 (#568)</code></a></li>
<li><a
href="32deb082c6"><code>Update
dependency c8 to v10 (#565)</code></a></li>
<li><a
href="d43ca1506d"><code>Update
dependency prettier to ~3.4.0 (#569)</code></a></li>
<li><a
href="9af465e598"><code>Update
dependency prettier to ~3.3.0 (#564)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-24 09:57:35 -04:00
kilavvy
f0e2f5bb7a
Fix: Correct typo in comment (#18361)
This pull request contains a couple of minor documentation fixes.

- Corrected a typo from `predicable` to `predictable` in a comment for
`DarkModeStrategy`.
- Applied minor formatting to a comment in a test file.

These changes help improve code clarity and maintainability.
2025-06-21 19:40:11 +00:00
depfu[bot]
d788657774
Update eslint 9.27.0 → 9.29.0 (minor) (#18360)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.27.0 → 9.29.0) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.29.0">9.29.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="f686fcb51e"><code
class="notranslate">f686fcb</code></a> feat: add <code
class="notranslate">ecmaVersion: 2026</code>, parsing <code
class="notranslate">using</code> and <code class="notranslate">await
using</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19832">#19832</a>)
(Milos Djermanovic)</li>
<li>
<a
href="19cdd226bb"><code
class="notranslate">19cdd22</code></a> feat: prune suppressions for
non-existent files (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19825">#19825</a>)
(TKDev7)</li>
<li>
<a
href="b3d720f82f"><code
class="notranslate">b3d720f</code></a> feat: add ES2025 globals (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19835">#19835</a>)
(fisker Cheung)</li>
<li>
<a
href="677a2837a1"><code
class="notranslate">677a283</code></a> feat: add auto-accessor fields
support to class-methods-use-this (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19789">#19789</a>)
(sethamus)</li>
<li>
<a
href="dbba0589f5"><code
class="notranslate">dbba058</code></a> feat: allow global type
declaration in <code class="notranslate">no-var</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19714">#19714</a>)
(Remco Haszing)</li>
<li>
<a
href="342bd29e1a"><code
class="notranslate">342bd29</code></a> feat: ignore type annotations in
no-restricted-globals (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19781">#19781</a>)
(sethamus)</li>
<li>
<a
href="786bcd1365"><code
class="notranslate">786bcd1</code></a> feat: add allowProperties option
to no-restricted-properties (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19772">#19772</a>)
(sethamus)</li>
<li>
<a
href="05b66d05bd"><code
class="notranslate">05b66d0</code></a> feat: add <code
class="notranslate">sourceCode.isGlobalReference(node)</code> method (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19695">#19695</a>)
(Nitin Kumar)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="85c082c54b"><code
class="notranslate">85c082c</code></a> fix: explicit matching behavior
with negated patterns and arrays (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19845">#19845</a>)
(Milos Djermanovic)</li>
<li>
<a
href="9bda4a9bf1"><code
class="notranslate">9bda4a9</code></a> fix: fix <code
class="notranslate">LintOptions.filterCodeBlock</code> types (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19837">#19837</a>)
(ntnyq)</li>
<li>
<a
href="7ab77a2c76"><code
class="notranslate">7ab77a2</code></a> fix: correct breaking deprecation
of FlatConfig type (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19826">#19826</a>)
(Logicer)</li>
<li>
<a
href="1ba33181ab"><code
class="notranslate">1ba3318</code></a> fix: add <code
class="notranslate">language</code> and <code
class="notranslate">dialects</code> to <code
class="notranslate">no-use-before-define</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19808">#19808</a>)
(Francesco Trotta)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="00e3e6ad13"><code
class="notranslate">00e3e6a</code></a> docs: add support for custom name
parameter to <code class="notranslate">includeIgnoreFile</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19795">#19795</a>)
(루밀LuMir)</li>
<li>
<a
href="3aed0756ed"><code
class="notranslate">3aed075</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="a2f888d679"><code
class="notranslate">a2f888d</code></a> docs: enhance documentation with
links and fix typos (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19761">#19761</a>)
(루밀LuMir)</li>
<li>
<a
href="53c3235ba1"><code
class="notranslate">53c3235</code></a> docs: update to clarify prompt
usage (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19748">#19748</a>)
(Jennifer Davis)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="5c114c962f"><code
class="notranslate">5c114c9</code></a> chore: upgrade @eslint/js@9.29.0
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19851">#19851</a>)
(Milos Djermanovic)</li>
<li>
<a
href="acf2201a06"><code
class="notranslate">acf2201</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="a806994263"><code
class="notranslate">a806994</code></a> refactor: Remove eslintrc from
flat config functionality (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19833">#19833</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="152ed51329"><code
class="notranslate">152ed51</code></a> test: switch to flat config mode
in code path analysis tests (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19824">#19824</a>)
(Milos Djermanovic)</li>
<li>
<a
href="b647239272"><code
class="notranslate">b647239</code></a> chore: Update first-party
dependencies faster with Renovate (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19822">#19822</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="7abe42e2de"><code
class="notranslate">7abe42e</code></a> refactor: SafeEmitter -&gt;
SourceCodeVisitor (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19708">#19708</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="e392895967"><code
class="notranslate">e392895</code></a> perf: improve time complexity of
<code class="notranslate">getLocFromIndex</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19782">#19782</a>)
(루밀LuMir)</li>
<li>
<a
href="0ed289c5ce"><code
class="notranslate">0ed289c</code></a> chore: remove accidentally
committed file (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19807">#19807</a>)
(Francesco Trotta)</li>
</ul></blockquote>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.28.0">9.28.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="b0674be94e"><code
class="notranslate">b0674be</code></a> feat: Customization of
serialization for languageOptions (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19760">#19760</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="a95721f106"><code
class="notranslate">a95721f</code></a> feat: Add <code
class="notranslate">--pass-on-unpruned-suppressions</code> CLI option
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19773">#19773</a>)
(Milos Djermanovic)</li>
<li>
<a
href="bfd0e7a395"><code
class="notranslate">bfd0e7a</code></a> feat: support TypeScript syntax
in <code class="notranslate">no-use-before-define</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19566">#19566</a>)
(Tanuj Kanti)</li>
<li>
<a
href="68c61c093a"><code
class="notranslate">68c61c0</code></a> feat: support TS syntax in <code
class="notranslate">no-shadow</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19565">#19565</a>)
(Nitin Kumar)</li>
<li>
<a
href="0f773ef248"><code
class="notranslate">0f773ef</code></a> feat: support TS syntax in <code
class="notranslate">no-magic-numbers</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19561">#19561</a>)
(Nitin Kumar)</li>
<li>
<a
href="c4a6b60518"><code
class="notranslate">c4a6b60</code></a> feat: add allowTypeAnnotation to
func-style (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19754">#19754</a>)
(sethamus)</li>
<li>
<a
href="b03ad176f1"><code
class="notranslate">b03ad17</code></a> feat: add TypeScript support to
<code class="notranslate">prefer-arrow-callback</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19678">#19678</a>)
(Tanuj Kanti)</li>
<li>
<a
href="bc3c3313ce"><code
class="notranslate">bc3c331</code></a> feat: ignore overloaded function
declarations in func-style rule (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19755">#19755</a>)
(sethamus)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="eea3e7eb1c"><code
class="notranslate">eea3e7e</code></a> fix: Remove configured global
variables from <code class="notranslate">GlobalScope#implicit</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19779">#19779</a>)
(Milos Djermanovic)</li>
<li>
<a
href="a467de39f6"><code
class="notranslate">a467de3</code></a> fix: update context.report types
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19751">#19751</a>)
(Nitin Kumar)</li>
<li>
<a
href="fd467bb892"><code
class="notranslate">fd467bb</code></a> fix: remove interopDefault to use
jiti's default (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19697">#19697</a>)
(sethamus)</li>
<li>
<a
href="72d16e3066"><code
class="notranslate">72d16e3</code></a> fix: avoid false positive in
<code class="notranslate">no-unassigned-vars</code> for declare module
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19746">#19746</a>)
(Azat S.)</li>
<li>
<a
href="81c3c93626"><code
class="notranslate">81c3c93</code></a> fix: curly types (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19750">#19750</a>)
(Eli)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="3ec208233f"><code
class="notranslate">3ec2082</code></a> docs: Nested arrays in files
config entry (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19799">#19799</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="89a65b07f6"><code
class="notranslate">89a65b0</code></a> docs: clarify how config arrays
can apply to subsets of files (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19788">#19788</a>)
(Shais Ch)</li>
<li>
<a
href="2ba8a0d75c"><code
class="notranslate">2ba8a0d</code></a> docs: Add description of
meta.namespace to plugin docs (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19798">#19798</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="59dd7e6b28"><code
class="notranslate">59dd7e6</code></a> docs: update <code
class="notranslate">func-style</code> with examples (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19793">#19793</a>)
(Tanuj Kanti)</li>
<li>
<a
href="e9129e0799"><code
class="notranslate">e9129e0</code></a> docs: add global scope's <code
class="notranslate">implicit</code> field to Scope Manager docs (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19770">#19770</a>)
(Milos Djermanovic)</li>
<li>
<a
href="52f5b7a0af"><code
class="notranslate">52f5b7a</code></a> docs: fix minor typos and add
links (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19743">#19743</a>)
(루밀LuMir)</li>
<li>
<a
href="00716a339e"><code
class="notranslate">00716a3</code></a> docs: upfront recommend against
using the no-return-await rule (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19727">#19727</a>)
(Mike DiDomizio)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="175b7b83fc"><code
class="notranslate">175b7b8</code></a> chore: upgrade to <code
class="notranslate">@eslint/js@9.28.0</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19802">#19802</a>)
(Francesco Trotta)</li>
<li>
<a
href="844f5a69dc"><code
class="notranslate">844f5a6</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="62b1c1bc79"><code
class="notranslate">62b1c1b</code></a> chore: update globals to v16 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19791">#19791</a>)
(Nitin Kumar)</li>
<li>
<a
href="e8a1cb8f7f"><code
class="notranslate">e8a1cb8</code></a> chore: ignore jiti-v2.0 &amp;
jiti-v2.1 for renovate (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19786">#19786</a>)
(Nitin Kumar)</li>
<li>
<a
href="43d39754b6"><code
class="notranslate">43d3975</code></a> chore: Add Copilot Instructions
file (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19753">#19753</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="2dfb5ebef4"><code
class="notranslate">2dfb5eb</code></a> test: update <code
class="notranslate">SourceCodeTraverser</code> tests (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19763">#19763</a>)
(Milos Djermanovic)</li>
<li>
<a
href="5bc21f9e8e"><code
class="notranslate">5bc21f9</code></a> chore: add <code
class="notranslate">*.code-workspace</code> to <code
class="notranslate">.gitignore</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19771">#19771</a>)
(루밀LuMir)</li>
<li>
<a
href="f4fa40eb4b"><code
class="notranslate">f4fa40e</code></a> refactor: NodeEventGenerator
-&gt; SourceCodeTraverser (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19679">#19679</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="0f49329b4a"><code
class="notranslate">0f49329</code></a> refactor: use a service to emit
warnings (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19725">#19725</a>)
(Francesco Trotta)</li>
<li>
<a
href="20a9e59438"><code
class="notranslate">20a9e59</code></a> chore: update dependency shelljs
to ^0.10.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19740">#19740</a>)
(renovate[bot])</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="b9080cf28d...edf232b680">See
the full diff on Github</a>. The new version differs by 58 commits:</p>
<ul>
<li><a
href="edf232b680"><code>9.29.0</code></a></li>
<li><a
href="c2414b647b"><code>Build:
changelog update for 9.29.0</code></a></li>
<li><a
href="5c114c962f"><code>chore:
upgrade @eslint/js@9.29.0 (#19851)</code></a></li>
<li><a
href="acf2201a06"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="f686fcb51e"><code>feat:
add `ecmaVersion: 2026`, parsing `using` and `await using`
(#19832)</code></a></li>
<li><a
href="85c082c54b"><code>fix:
explicit matching behavior with negated patterns and arrays
(#19845)</code></a></li>
<li><a
href="00e3e6ad13"><code>docs:
add support for custom name parameter to `includeIgnoreFile`
(#19795)</code></a></li>
<li><a
href="9bda4a9bf1"><code>fix:
fix `LintOptions.filterCodeBlock` types (#19837)</code></a></li>
<li><a
href="a806994263"><code>refactor:
Remove eslintrc from flat config functionality (#19833)</code></a></li>
<li><a
href="19cdd226bb"><code>feat:
prune suppressions for non-existent files (#19825)</code></a></li>
<li><a
href="b3d720f82f"><code>feat:
add ES2025 globals (#19835)</code></a></li>
<li><a
href="677a2837a1"><code>feat:
add auto-accessor fields support to class-methods-use-this
(#19789)</code></a></li>
<li><a
href="3aed0756ed"><code>docs:
Update README</code></a></li>
<li><a
href="7ab77a2c76"><code>fix:
correct breaking deprecation of FlatConfig type (#19826)</code></a></li>
<li><a
href="a2f888d679"><code>docs:
enhance documentation with links and fix typos (#19761)</code></a></li>
<li><a
href="dbba0589f5"><code>feat:
allow global type declaration in `no-var` (#19714)</code></a></li>
<li><a
href="152ed51329"><code>test:
switch to flat config mode in code path analysis tests
(#19824)</code></a></li>
<li><a
href="b647239272"><code>chore:
Update first-party dependencies faster with Renovate
(#19822)</code></a></li>
<li><a
href="7abe42e2de"><code>refactor:
SafeEmitter -&gt; SourceCodeVisitor (#19708)</code></a></li>
<li><a
href="342bd29e1a"><code>feat:
ignore type annotations in no-restricted-globals
(#19781)</code></a></li>
<li><a
href="e392895967"><code>perf:
improve time complexity of `getLocFromIndex` (#19782)</code></a></li>
<li><a
href="1ba33181ab"><code>fix:
add `language` and `dialects` to `no-use-before-define`
(#19808)</code></a></li>
<li><a
href="786bcd1365"><code>feat:
add allowProperties option to no-restricted-properties
(#19772)</code></a></li>
<li><a
href="05b66d05bd"><code>feat:
add `sourceCode.isGlobalReference(node)` method (#19695)</code></a></li>
<li><a
href="53c3235ba1"><code>docs:
update to clarify prompt usage (#19748)</code></a></li>
<li><a
href="0ed289c5ce"><code>chore:
remove accidentally committed file (#19807)</code></a></li>
<li><a
href="f341f21e02"><code>9.28.0</code></a></li>
<li><a
href="779dda93a2"><code>Build:
changelog update for 9.28.0</code></a></li>
<li><a
href="175b7b83fc"><code>chore:
upgrade to `@eslint/js@9.28.0` (#19802)</code></a></li>
<li><a
href="844f5a69dc"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="b0674be94e"><code>feat:
Customization of serialization for languageOptions
(#19760)</code></a></li>
<li><a
href="3ec208233f"><code>docs:
Nested arrays in files config entry (#19799)</code></a></li>
<li><a
href="89a65b07f6"><code>docs:
clarify how config arrays can apply to subsets of files
(#19788)</code></a></li>
<li><a
href="2ba8a0d75c"><code>docs:
Add description of meta.namespace to plugin docs
(#19798)</code></a></li>
<li><a
href="eea3e7eb1c"><code>fix:
Remove configured global variables from `GlobalScope#implicit`
(#19779)</code></a></li>
<li><a
href="a95721f106"><code>feat:
Add `--pass-on-unpruned-suppressions` CLI option
(#19773)</code></a></li>
<li><a
href="a467de39f6"><code>fix:
update context.report types (#19751)</code></a></li>
<li><a
href="59dd7e6b28"><code>docs:
update `func-style` with examples (#19793)</code></a></li>
<li><a
href="62b1c1bc79"><code>chore:
update globals to v16 (#19791)</code></a></li>
<li><a
href="bfd0e7a395"><code>feat:
support TypeScript syntax in `no-use-before-define`
(#19566)</code></a></li>
<li><a
href="68c61c093a"><code>feat:
support TS syntax in `no-shadow` (#19565)</code></a></li>
<li><a
href="e8a1cb8f7f"><code>chore:
ignore jiti-v2.0 &amp; jiti-v2.1 for renovate (#19786)</code></a></li>
<li><a
href="0f773ef248"><code>feat:
support TS syntax in `no-magic-numbers` (#19561)</code></a></li>
<li><a
href="43d39754b6"><code>chore:
Add Copilot Instructions file (#19753)</code></a></li>
<li><a
href="c4a6b60518"><code>feat:
add allowTypeAnnotation to func-style (#19754)</code></a></li>
<li><a
href="fd467bb892"><code>fix:
remove interopDefault to use jiti&#39;s default (#19697)</code></a></li>
<li><a
href="2dfb5ebef4"><code>test:
update `SourceCodeTraverser` tests (#19763)</code></a></li>
<li><a
href="b03ad176f1"><code>feat:
add TypeScript support to `prefer-arrow-callback`
(#19678)</code></a></li>
<li><a
href="e9129e0799"><code>docs:
add global scope&#39;s `implicit` field to Scope Manager docs
(#19770)</code></a></li>
<li><a
href="bc3c3313ce"><code>feat:
ignore overloaded function declarations in func-style rule
(#19755)</code></a></li>
<li><a
href="5bc21f9e8e"><code>chore:
add `*.code-workspace` to `.gitignore` (#19771)</code></a></li>
<li><a
href="72d16e3066"><code>fix:
avoid false positive in `no-unassigned-vars` for declare module
(#19746)</code></a></li>
<li><a
href="f4fa40eb4b"><code>refactor:
NodeEventGenerator -&gt; SourceCodeTraverser (#19679)</code></a></li>
<li><a
href="81c3c93626"><code>fix:
curly types (#19750)</code></a></li>
<li><a
href="52f5b7a0af"><code>docs:
fix minor typos and add links (#19743)</code></a></li>
<li><a
href="0f49329b4a"><code>refactor:
use a service to emit warnings (#19725)</code></a></li>
<li><a
href="20a9e59438"><code>chore:
update dependency shelljs to ^0.10.0 (#19740)</code></a></li>
<li><a
href="00716a339e"><code>docs:
upfront recommend against using the no-return-await rule
(#19727)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-20 14:03:07 -04:00
depfu[bot]
1a35fbce1a
Update @vitejs/plugin-react 4.5.0 → 4.5.2 (patch) (#18352)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (4.5.0 → 4.5.2) ·
[Repo](https://github.com/vitejs/vite) ·
[Changelog](https://github.com/vitejs/vite/blob/main/packages/plugin-react/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vitejs/vite/releases/tag/v4.5.2">4.5.2</a></h4>

<blockquote><p dir="auto">Please refer to <a
href="https://bounce.depfu.com/github.com/vitejs/vite/blob/v4.5.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p></blockquote>
<h4><a
href="https://github.com/vitejs/vite/releases/tag/v4.5.1">4.5.1</a></h4>

<blockquote><p dir="auto">Please refer to <a
href="https://bounce.depfu.com/github.com/vitejs/vite/blob/v4.5.1/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>













---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-20 06:23:57 -04:00
Jordan Pittman
c5a997cbdb
Document --watch=always in the CLI (#18337)
Closes https://github.com/tailwindlabs/tailwindcss.com/issues/1835

We had this in v3 as an undocumented option. We still have it in v4 but
it's not documented under the CLI usage but should be. This PR adds
this.

**before**
<img width="803" alt="Screenshot 2025-06-18 at 09 41 40"
src="https://github.com/user-attachments/assets/c3becf11-e31d-4355-9d23-bddd0b2fc4a6"
/>

**after**
<img width="1152" alt="Screenshot 2025-06-18 at 09 41 20"
src="https://github.com/user-attachments/assets/4f61a156-680d-4f39-b92d-7f0f63270689"
/>
2025-06-19 14:52:47 -04:00
Jordan Pittman
75cbfc251d Update changelog 2025-06-18 12:38:54 -04:00
Henrik Eneroth
f4a7eea6cc
Extract candidates with variants in Clojure/ClojureScript keywords (#18338)
## Summary

Taking a shot at fixing my own complaint from #18336. 

Added failing test and fix for extracting classes containing a
pseudo-class from Clojure keywords. Eg., `:hover:text` ->
`"hover:text"`. This would previously produce `["hover", "text"]`.

## Test plan

- Add a failing test. 
- Verify that it fails with `cargo test`. 
- Add fix. 
- Verify that `cargo test` has no further complaints. 

ATT: @RobinMalfait

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-06-18 12:38:27 -04:00
depfu[bot]
63b5d7bdab
Update @playwright/test 1.52.0 → 1.53.0 (minor) (#18332)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​playwright/test (1.52.0 → 1.53.0) ·
[Repo](https://github.com/Microsoft/playwright)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/microsoft/playwright/releases/tag/v1.53.0">1.53.0</a></h4>

<blockquote><h2 dir="auto">Trace Viewer and HTML Reporter Updates</h2>
<ul dir="auto">
<li>
<p dir="auto">New Steps in Trace Viewer and HTML reporter:<br>
<a
href="https://private-user-images.githubusercontent.com/17984549/451416137-1963ff7d-4070-41be-a79b-4333176921a2.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDk2NzA4MjAsIm5iZiI6MTc0OTY3MDUyMCwicGF0aCI6Ii8xNzk4NDU0OS80NTE0MTYxMzctMTk2M2ZmN2QtNDA3MC00MWJlLWE3OWItNDMzMzE3NjkyMWEyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA2MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNjExVDE5MzUyMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTQ5YjQzYWVjZGQ5Y2E4NjgwNWY4NWJmNzg0ODA4YmNlMTg2ZjdmNzliODllNmU3YmEzOGIzM2JlMjg2Y2ExODcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.tF0t1fNEXfC-WalH0bVazGMGwZ_FnsBkxJgn7Dk6za0"><img
src="https://private-user-images.githubusercontent.com/17984549/451416137-1963ff7d-4070-41be-a79b-4333176921a2.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDk2NzA4MjAsIm5iZiI6MTc0OTY3MDUyMCwicGF0aCI6Ii8xNzk4NDU0OS80NTE0MTYxMzctMTk2M2ZmN2QtNDA3MC00MWJlLWE3OWItNDMzMzE3NjkyMWEyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA2MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNjExVDE5MzUyMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTQ5YjQzYWVjZGQ5Y2E4NjgwNWY4NWJmNzg0ODA4YmNlMTg2ZjdmNzliODllNmU3YmEzOGIzM2JlMjg2Y2ExODcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.tF0t1fNEXfC-WalH0bVazGMGwZ_FnsBkxJgn7Dk6za0"
alt="New Trace Viewer Steps" height="500" style="max-width:
100%;"></a></p>
</li>
<li>
<p dir="auto">New option in <code class="notranslate">'html'</code>
reporter to set the title of a specific test run:</p>
<div class="highlight highlight-source-js" dir="auto"><pre
class="notranslate"><span class="pl-k">import</span> <span
class="pl-kos">{</span> <span class="pl-s1">defineConfig</span> <span
class="pl-kos">}</span> <span class="pl-k">from</span> <span
class="pl-s">'@playwright/test'</span><span class="pl-kos">;</span>

<span class="pl-k">export</span> <span class="pl-k">default</span> <span
class="pl-en">defineConfig</span><span class="pl-kos">(</span><span
class="pl-kos">{</span>
<span class="pl-c1">reporter</span>: <span class="pl-kos">[</span><span
class="pl-kos">[</span><span class="pl-s">'html'</span><span
class="pl-kos">,</span> <span class="pl-kos">{</span> <span
class="pl-c1">title</span>: <span class="pl-s">'Custom test run
#1028'</span> <span class="pl-kos">}</span><span
class="pl-kos">]</span><span class="pl-kos">]</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span
class="pl-kos">;</span></pre></div>
</li>
</ul>
<h2 dir="auto">Miscellaneous</h2>
<ul dir="auto">
<li>
<p dir="auto">New option <a
href="https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-path-option-kind"><code
class="notranslate">kind</code></a> in <a
href="https://playwright.dev/docs/api/class-testinfo#test-info-snapshot-path">testInfo.snapshotPath()</a>
controls which snapshot path template is used.</p>
</li>
<li>
<p dir="auto">New method <a
href="https://playwright.dev/docs/api/class-locator#locator-describe">locator.describe()</a>
to describe a locator. Used for trace viewer and reports.</p>
<div class="highlight highlight-source-js" dir="auto"><pre
class="notranslate"><span class="pl-k">const</span> <span
class="pl-s1">button</span> <span class="pl-c1">=</span> <span
class="pl-s1">page</span><span class="pl-kos">.</span><span
class="pl-en">getByTestId</span><span class="pl-kos">(</span><span
class="pl-s">'btn-sub'</span><span class="pl-kos">)</span><span
class="pl-kos">.</span><span class="pl-en">describe</span><span
class="pl-kos">(</span><span class="pl-s">'Subscribe button'</span><span
class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">await</span> <span class="pl-s1">button</span><span
class="pl-kos">.</span><span class="pl-en">click</span><span
class="pl-kos">(</span><span class="pl-kos">)</span><span
class="pl-kos">;</span></pre></div>
</li>
<li>
<p dir="auto"><code class="notranslate">npx playwright install
--list</code> will now list all installed browsers, versions and
locations.</p>
</li>
</ul>
<h2 dir="auto">Browser Versions</h2>
<ul dir="auto">
<li>Chromium 138.0.7204.4</li>
<li>Mozilla Firefox 139.0</li>
<li>WebKit 18.5</li>
</ul>
<p dir="auto">This version was also tested against the following stable
channels:</p>
<ul dir="auto">
<li>Google Chrome 137</li>
<li>Microsoft Edge 137</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="471930b1ce...682277722c">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-17 14:48:33 -04:00
Jordan Pittman
5fc6698b7b
Add heuristic to skip candidate migrations inside emit(…) (#18330)
Fixes #18318
2025-06-17 12:49:33 -04:00
leopardracer
44534963c3
Small Typo Fixes and Comment Improvements (#18328)
Description:
This pull request corrects minor typos in comments and improves clarity
in two files:
- Fixes a typo in a comment within migrate-js-config.ts ("migrateable" →
"migratable").
- Refines a comment in wasm.test.ts for better readability.

No functional code changes are included.
2025-06-17 10:46:08 -04:00
depfu[bot]
bab16aeb8c
Update @napi-rs/wasm-runtime 0.2.10 → 0.2.11 (minor) (#18316)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​napi-rs/wasm-runtime (0.2.10 → 0.2.11) ·
[Repo](https://github.com/napi-rs/napi-rs)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-16 10:27:00 -04:00
Robin Malfait
7f97179b66
update changelog
drop the `:`
2025-06-11 23:21:57 +02:00
Robin Malfait
2ebaff214a
make changelog entry consistent 2025-06-11 23:07:12 +02:00
Robin Malfait
d06bbb80da
Prepare v4.1.10 (#18290) 2025-06-11 22:26:51 +02:00
Robin Malfait
ddb0beff66
Fix missing space around - when using % regression (#18289)
This PR fixes a regression we shipped in v4.1.9, when using arbitrary
values and injecting spaces around operator.

When you use `w-[calc(100%-var(--foo))]`, you expect that this generates
valid CSS:

```css
width: calc(100% - var(--foo));
```

But due to a regression, we generated:

```css
width: calc(100%-var(--foo));
```

Which is invalid CSS.

This is because the algorithm we used to know when we had to inject a
space around the `-` didn't take the `%` sign into account.

We also didn't handle uppercase units like `123PX` properly. This PR
fixes both issues.

## Test plan

1. Added a regression test for the `%`
2. Added a regression test for uppercase units like `123PX`

Fixes: #18288
2025-06-11 20:18:14 +00:00
depfu[bot]
427649eee4
Update @types/react-dom 19.1.5 → 19.1.6 (patch) (#18284)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react-dom (19.1.5 → 19.1.6) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-11 13:57:02 -04:00
Robin Malfait
b88371a309
Prepare v4.1.9 (#18285)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-06-11 15:57:38 +02:00
Rózsa Zoltán
aa817fb6de
fix: don't break CSS keywords when formatting math expressions (#18220)
Fixes #18219

## Summary

In an arbitrary value, if there's a non-numeric character both before
and after a hyphen, there's no need for a space.

## Test plan

`decodeArbitraryValue` will correctly format special CSS values like
`fit-content`. I believe spaces are only necessary if there's a digit
either before or after the hyphen.

```js
decodeArbitraryValue('min(fit-content,calc(100dvh-4rem))')
```

This way, the result of the following arbitrary value will also be
correct:

```html
<div class="min-h-[min(fit-content,calc(100dvh-4rem))]"></div>
```

```css
.min-h-\[min\(fit-content\,calc\(100dvh-4rem\)\)\] {
  min-height: min(fit-content, calc(100dvh - 4rem));
}
```

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-06-10 10:38:31 -04:00
depfu[bot]
da0895655e
Update @types/react 19.1.4 → 19.1.6 (patch) (#18267)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react (19.1.4 → 19.1.6) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-10 07:07:41 -04:00
Cyril Duez
bea843c90a
CSS Parser: Handle string with semi-colon in custom properties. (#18251)
Strings are not parsed correctly for custom properties which makes the
following CSS raise an `Unterminated string: ";"` error:

```css
:root { 
  --custom: 'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==';
}
```

According to the spec, we should accept semi-colon as long as they are
not at the top level.
> The allowed syntax for [custom
properties](https://drafts.csswg.org/css-variables/#custom-property) is
extremely permissive. The <declaration-value> production matches any
sequence of one or more tokens, so long as the sequence does not contain
bad-string-token, bad-url-token, unmatched )-token, ]-token, or }-token,
or top-level semicolon-token tokens or delim-token tokens with a value
of "!".

Extract from: https://drafts.csswg.org/css-variables/#syntax

I was only able to reproduce with **tailwindcss v4**, the previous
version seems to support this. This issue is mitigated by the fact that
even if you want to use a data URL in a custom property, you would need
to wrap the value in a `url()` anyway:

```css
:root { 
  --my-icon-url: url('data:image/svg+xml;base64,...==');
}

.icon {
  background-image: var(--my-icon-url);
}
```

Which works perfectly fine with the current/latest version (v4.1.8).

The fix suggested is to share the same code between regular property and
custom property when it comes to detect that the value is a string
starting with a `SINGLE_QUOTE` or `DOUBLE_QUOTE`. I have moved the
existing code in a `findEndStringIdx` which returns the position of the
ending single/double quote.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-06-09 14:03:44 +00:00
depfu[bot]
fd95af4284
Update bun 1.2.13 → 1.2.15 (patch) (#18252)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ bun (1.2.13 → 1.2.15) · [Repo](https://github.com/oven-sh/bun)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-09 09:23:05 -04:00
Robin Malfait
f0f42f62c0
Use shards when running integration tests (#18246)
This PR splits CI integration tests in 3 shards to reduce the time it
takes to run them to hopefully prevent timeouts on a single step.
2025-06-06 19:43:17 +00:00
depfu[bot]
ada85b1f24
Update turbo 2.5.3 → 2.5.4 (patch) (#18243)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ turbo (2.5.3 → 2.5.4) ·
[Repo](https://github.com/turborepo/turbo)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-06 12:14:04 -04:00
depfu[bot]
21ece6c6da
Update all of nextjs 15.3.2 → 15.3.3 (patch) (#18234)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint-config-next (15.3.2 → 15.3.3)





Sorry, we couldn't find anything useful about this release.



#### ✳️ next (15.3.2 → 15.3.3) ·
[Repo](https://github.com/vercel/next.js)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.3.3">15.3.3</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon
octicon-info mr-2" viewbox="0 0 16 16" version="1.1" width="16"
height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0
8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1
7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1
0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0
2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>Reinstate <code class="notranslate">vary</code> (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/79939">#79939</a>)</li>
<li>fix(next-swc): Fix interestingness detection for React Compiler (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/79558">#79558</a>)</li>
<li>fix(next-swc): Fix react compiler usefulness detector (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/79480">#79480</a>)</li>
<li>fix(dev-overlay): Better handle edge-case file paths in launchEditor
(<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/79526">#79526</a>)</li>
<li>Client router should discard stale prefetch entries for static pages
(<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/79362">#79362</a>)</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to <a
href="https://bounce.depfu.com/github.com/gaojude">@gaojude</a>, <a
href="https://bounce.depfu.com/github.com/kdy1">@kdy1</a>, <a
href="https://bounce.depfu.com/github.com/bgw">@bgw</a>, and <a
href="https://bounce.depfu.com/github.com/unstubbable">@unstubbable</a>
for helping!</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="d9ec4a4b57...3ab8db7383">See
the full diff on Github</a>. The new version differs by 8 commits:</p>
<ul>
<li><a
href="3ab8db7383"><code>v15.3.3</code></a></li>
<li><a
href="18c8113ebd"><code>[backport]
Reinstate `vary` (#79939)</code></a></li>
<li><a
href="e18212f546"><code>re-enable
vary header deploy test (#79753)</code></a></li>
<li><a
href="ec202eccf0"><code>Revert
&quot;[next-server] skip setting vary header for basic routes&quot;
(#79426)</code></a></li>
<li><a
href="e2f264fdce"><code>fix(next-swc):
Fix interestingness detection for React Compiler (15.3)
(#79558)</code></a></li>
<li><a
href="562fac78da"><code>fix(next-swc):
Fix react compiler usefulness detector (15.3) (#79480)</code></a></li>
<li><a
href="06097fd7bb"><code>fix(dev-overlay):
Better handle edge-case file paths in launchEditor
(#79526)</code></a></li>
<li><a
href="bda731fa96"><code>Client
router should discard stale prefetch entries for static pages
(#79362)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-05 17:02:09 -04:00
depfu[bot]
63f6a6c4c8
Update @types/bun 1.2.13 → 1.2.15 (patch) (#18222)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/bun (1.2.13 → 1.2.15) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-05 13:30:12 -04:00
depfu[bot]
288ab3e879
Update postcss 8.5.3 → 8.5.4 (patch) (#18225)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ postcss (8.5.3 → 8.5.4) ·
[Repo](https://github.com/postcss/postcss) ·
[Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/postcss/postcss/releases/tag/8.5.4">8.5.4</a></h4>

<blockquote><ul dir="auto">
<li>Fixed Parcel compatibility issue (by <a
href="https://bounce.depfu.com/github.com/git-sumitchaudhary">@git-sumitchaudhary</a>).</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="22c309d329...6cb4a6673f">See
the full diff on Github</a>. The new version differs by 23 commits:</p>
<ul>
<li><a
href="6cb4a6673f"><code>Release
8.5.4 version</code></a></li>
<li><a
href="ec5c1e0310"><code>Update
dependencies</code></a></li>
<li><a
href="e85e9385c8"><code>Fix
code format</code></a></li>
<li><a
href="50542335f2"><code>fixed
error at line 401 (#2046)</code></a></li>
<li><a
href="0538b6309e"><code>docs:
Update README.md (#2044)</code></a></li>
<li><a
href="b5f407f397"><code>Add
`postcss-fontsource-url` to plugins (#2043)</code></a></li>
<li><a
href="94b5260be6"><code>Clarify
documentation for `node.source.end.offset` (#2032)</code></a></li>
<li><a
href="a20724af14"><code>Fix
Markdown syntax</code></a></li>
<li><a
href="cf6b969751"><code>fix:
add a mention for postcssense extension (#2040)</code></a></li>
<li><a
href="ff48c29ebb"><code>Increase
size</code></a></li>
<li><a
href="f82c2a6090"><code>Add
`Input#fromLineAndColumn`, `CssSyntaxError.input.offset` and
`CssSyntaxError.input.endOffset` (#2034)</code></a></li>
<li><a
href="7892a29ff8"><code>Fix
missing offset in `Node#positionInside`, `Node#positionBy` and
`Node#rangeBy` (#2033)</code></a></li>
<li><a
href="875f1fb7f8"><code>Fix
`opts` argument of `Node#rangeBy()` and `Node#positionBy()` cannot be
omitted (#2031)</code></a></li>
<li><a
href="25443a5930"><code>Update
dependencies</code></a></li>
<li><a
href="0a76df5f60"><code>add
postcss-auto-var-fallback fallback plugin to plugins.md
(#2026)</code></a></li>
<li><a
href="3fc6388e91"><code>Update
Nano ID</code></a></li>
<li><a
href="368bd00920"><code>Change
in type definition of property `css` of `result` (#2025)</code></a></li>
<li><a
href="9229218b51"><code>Update
dependencies</code></a></li>
<li><a
href="6c2a808c4a"><code>Clean
up code</code></a></li>
<li><a
href="31a5de71d1"><code>Return
type of method `raw` of `Stringifier` can be boolean, as the property
`semicolon` can be boolean (#2024)</code></a></li>
<li><a
href="c5058912f2"><code>There
are no other `props` with non-string type.</code></a></li>
<li><a
href="a9cf39e7fe"><code>The
return type of the method rawValue in Stringifier should be `any`
instead of `string`</code></a></li>
<li><a
href="7ac43db0c3"><code>Update
ESLint config</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-06-05 09:34:20 -04:00
nakanoh
8bfbac5d20
docs: fix typo in container.ts comment ("a the" -> "the") (#18223)
## Description
Fixed a typo in the container.ts comment where "a the" was incorrectly
written as "the" for better readability.

## Test Plan
- [x] Verified the change is purely documentation-related and doesn't
affect functionality
- [x] Ensured the comment is more readable and grammatically correct
- [x] No tests needed as this is a documentation-only change

## Changes
- Modified comment in `packages/tailwindcss/src/compat/container.ts`
- Changed "When setting a the `screens` in v3" to "When setting the
`screens` in v3"

Co-authored-by: 中野 博文 <hirofumi0082@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-06-05 09:21:56 -04:00
nakanoh
0c1c0c4aa5
docs: fix typo ("a arbitrary" -> "an arbitrary") (#18227)
## Summary
- Fixes a typo: "a arbitrary" → "an arbitrary" in a comment/description.

## Details
- This is a documentation-only change. No code logic is affected.

## Test Plan
- N/A (doc-only)

Co-authored-by: 中野 博文 <hirofumi0082@gmail.com>
2025-06-05 09:30:31 +00:00
Robin Malfait
f425720c24
Do not migrate blur in wire:model.blur (#18216)
This PR fixes an issue where the `blur` in `wire:model.blur="…"` was
incorrectly migrated. We solved it by marking `wire:…` as an unsafe
region (`…` can be anything but whitespace).

Fixes: #18187

## Test plan

Added a test with this use case

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-06-03 18:27:59 +00:00
Robin Malfait
b3fde17eac
Migrate negative arbitrary values to negative bare values (#18212)
This PR adds some improvements to the upgrade tool where it can now also
migrate negative arbitrary values to negative bare values.

We already had support for the positive version of this:
```diff
- mb-[32rem]
+ mb-128
```

But now it can also handle negative values:
```diff
- mb-[-32rem]
+ -mb-128
```

The only tricky part here is that we had to hoist the `-` sign. Before
this PR, we were actually generating `mb--128` and that is invalid so it
was thrown out.

## Test plan

1. Added a test to ensure that the negative values are correctly
transformed.
2025-06-03 08:53:58 +00:00
depfu[bot]
191195af7e
Update @types/react-dom 19.1.3 → 19.1.5 (patch) (#18198)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react-dom (19.1.3 → 19.1.5) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-06-02 09:39:20 -04:00
depfu[bot]
54c86d4be7
Update @vitejs/plugin-react 4.4.1 → 4.5.0 (minor) (#18181)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (4.4.1 → 4.5.0) ·
[Repo](https://github.com/vitejs/vite) ·
[Changelog](https://github.com/vitejs/vite/blob/main/packages/plugin-react/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vitejs/vite/releases/tag/v4.5.0">4.5.0</a></h4>

<blockquote><p dir="auto">Please refer to <a
href="https://bounce.depfu.com/github.com/vitejs/vite/blob/v4.5.0/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>













---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-05-30 10:07:30 -04:00
Robin Malfait
31c0a21452
Improve upgrade migrations (#18184)
This PR fixes 2 issues with the migration tool where certain classes
weren't migrated. This PR fixes those 2 scenarios:

### Scenario 1

When you have an arbitrary opacity modifier that doesn't use `%`, but is
just a number typically between `0` and `1` then this was not converted
to the bare value equivalent before.

E.g.:

```html
<div class="bg-[#f00]/[0.16]"></dv>
```

Will now be converted to:

```html
<div class="bg-[#f00]/16"></dv>
```

### Scenario 2

Fixes a bug when a CSS function was used in a fallback value in the CSS
variable shorthand syntax. In that case we didn't migrate the class to
the new syntax.

This was because we assumed that a `(` was found, that we are dealing
with a CSS function.

E.g.: 
```html
<div class="w-[--spacing(1)]"></div>
                        ^  This indicates a CSS function, we should not be 
                           converting this to `w-(--spacing(1))`
```

But if a function was used as a fallback value, for example:

```html
<div class="bg-[--my-color,theme(colors.red.500)]"></dv>
```

Then we also didn't migrate it, but since the function call is in the
fallback, we can still migrate it.

Will now properly be converted to:

```html
<div class="bg-(--my-color,var(--color-red-500))"></dv>
```


## Test plan

1. Added a test for the first case
2. Added a test for the second case
3. Also added an integration-like test that runs all the migration steps
to make sure that the `theme(…)` in the fallback also gets updated to
`var(…)`. This one caught an issue because the `var(…)` wasn't handling
prefixes correctly.
2025-05-30 13:32:59 +00:00
depfu[bot]
3c629dec86
Update tsup 8.4.0 → 8.5.0 (minor) (#18125)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ tsup (8.4.0 → 8.5.0)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-05-29 13:38:00 -04:00
Robin Malfait
193eb84f6c
Release v4.1.8 (#18164)
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-05-28 17:01:27 +02:00
Robin Malfait
4bfacb33a0
Improve error messages when @apply fails (#18059)
This PR improves error messages when `@apply` fails. Right now it gives
you a generic error message that you cannot apply a certain utility.

```css
.foo {
  @apply bg-red-500;
}
```

Would result in:
```
Cannot apply unknown utility class: bg-red-500
```

However, there are some situations where we can give you more context
about what's happening.

### Missing `@import "tailwindcss"` or `@reference`

If you are in a Vue file for example, and you have the following code:
```vue
<template>
  <div class="foo"></div>
</template>

<style>
.foo {
  @apply bg-red-500;
}
</style>
```

Then this will now result in:
```
Cannot apply unknown utility class `bg-white`. Are you using CSS modules or similar and missing `@reference`? https://tailwindcss.com/docs/functions-and-directives#reference-directive
```

We do this by checking if we found a `@tailwind utilities` or
`@reference`. If not, we throw this more specific error.

### Explicitly excluded classes via `@source not inline('…')`

Or via the legacy `blocklist` from a JS config.

If you then have the following file:
```css
@import "tailwindcss";

@source not inline('bg-white');

.foo {
  @apply bg-white;
}
```

Then this will now result in:
```
Cannot apply utility class `bg-white` because it has been explicitly disabled: https://tailwindcss.com/docs/detecting-classes-in-source-files#explicitly-excluding-classes
```

We do this by checking if the class was marked as invalid.

### Applying unprefixed class in prefix mode

If you have the prefix option configured, but you are applying a
non-prefixed class, then we will show the following error:

Given this input:
```css
@import "tailwindcss" prefix(tw);

.foo {
  @apply underline;
}
```


The following error is thrown:
```
Cannot apply unprefixed utility class `underline`. Did you mean `tw:underline`?
```

### Applying known utilities with unknown variants

If you have unknown variants, then we will list them as well if the base
utility does compile correctly.

Given this input:

```css
@import "tailwindcss";

.foo {
  @apply hocus:hover:pocus:bg-red-500;
}
```

The following error is thrown:
```
Cannot apply utility class `hocus:hover:pocus:bg-red-500` because the `hocus` and `pocus` variants do not exist.
```

## Test plan

1. Everything behaves the same, but the error messages give more
details.
2. Updated tests with new error messages
3. Added new unit tests to verify the various scenarios
4. Added a Vue specific integration test with a `<style>…</style>` block
using `@apply`

[ci-all] There are some newlines here and there, let's verify that they
work identically on all platforms.

---------

Co-authored-by: Jonathan Reinink <jonathan@reinink.ca>
2025-05-27 20:19:14 +02:00
Jordan Pittman
58a6ad0e28
Ignore .pnpm-store in Oxide (#18163)
Fixes #18148
2025-05-27 13:02:10 -04:00
depfu[bot]
a37f6ba6a3
Update turbo 2.5.2 → 2.5.3 (patch) (#18157)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ turbo (2.5.2 → 2.5.3) ·
[Repo](https://github.com/turborepo/turbo)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-05-27 11:12:06 +02:00
Robin Malfait
1d4c263c73
Fix Haml pre-processing crash when there is no \n at the end of the file (#18155)
This PR fixes a Haml pre-processing issue where a crash occurs if there
is no trailing `\n` at the end of the file and the code before it was
considered Ruby code.

This happens in situations where Ruby code was used. E.g.:

```
- index = 0
- index += 1
```

In this situation when we see the `-` on the second line, then we will
find the whole indented block and parse it as Ruby code instead. The
block ands at the last `\n`, but since we reach the end of the file,
there is no `\n`. Right now we incorrectly reset the internal cursor to
the last known `\n` position. This means that the `start` of the Ruby
block will be _after_ the `end` of the Ruby block and the Haml parser
will crash.

To solve this, once we reach the end of the file, we don't reset the
cursor to the wrong position.

Fixes:
https://github.com/tailwindlabs/tailwindcss/issues/17379#issuecomment-2910108646

## Test plan

1. Added a regression test that did fail before the fix, and doesn't
anymore
2025-05-26 18:52:44 +02:00
Robin Malfait
5131237d67
Fix migrating mt-[0px] to -mt-[0px] instead of the other way around (#18154)
I was testing to upgrade tool on various random projects just to see how
it behaves. Then I noticed an odd migration...

This PR fixes an issue where the upgrade tool accidentally migrated
classes such as `mt-[0px]` to `-mt-[0px]`. The reason for this is
because we are trying to find a replacement, and the computed signature
for both of them are exactly the same.

- `mt-[0px]` translates to:

   ```css
   .x {
     margin-top: 0px;
   }
   ```

- `-mt-[0px]` translates to:

   ```css
   .x {
     margin-top: calc(0px * -1);
   }
   ```

   Which in turn translates to

   ```css
   .x {
     margin-top: 0px;
   }
   ```

   Notice that this is `0px`, not `-0px`.

Internally we use the roots of functional utilities to find
replacements. For intellisense purposes we typically show negative
versions before positive versions. This then means that we will try
`-mt-*` before `mt-*`. Because of the signature above, the `mt-[0px]`
was translated into `-mt-[0px]`.

We could solve this in a few ways. The first thing we can try is to make
sure that the signature is not the same and that `-mt-[0px]` actually
translates into `-0px` not `0px`.

This would solve our problem of the accidental migration. However, if we
_just_ sort the functional utilities roots such that the positive
versions exist before negative version and rely on the fact that
`-mt-[0px]` has the same signature. Then it also means that by doing
that we can migrate `-mt-[0px]` into `mt-[0px]` which is even better
because it's the same result and shorter.

## Test plan

1. Added a test to verify that `mt-[0px]` does not get migrated to
`-mt-[0px]`.
2. Added a test to verify that `-mt-[0px]` does get migrated to
`mt-[0px]`.
2025-05-26 16:29:22 +02:00
depfu[bot]
5d4e8f00fa
Update eslint 9.25.1 → 9.27.0 (minor) (#18132)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.25.1 → 9.27.0) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.27.0">9.27.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="d71e37f450"><code
class="notranslate">d71e37f</code></a> feat: Allow flags to be set in
ESLINT_FLAGS env variable (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19717">#19717</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="ba456e000e"><code
class="notranslate">ba456e0</code></a> feat: Externalize MCP server (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19699">#19699</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="07c1a7e839"><code
class="notranslate">07c1a7e</code></a> feat: add <code
class="notranslate">allowRegexCharacters</code> to <code
class="notranslate">no-useless-escape</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19705">#19705</a>)
(sethamus)</li>
<li>
<a
href="7bc6c71ca3"><code
class="notranslate">7bc6c71</code></a> feat: add no-unassigned-vars rule
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19618">#19618</a>)
(Jacob Bandes-Storch)</li>
<li>
<a
href="ee40364297"><code
class="notranslate">ee40364</code></a> feat: convert
no-array-constructor suggestions to autofixes (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19621">#19621</a>)
(sethamus)</li>
<li>
<a
href="32957cde72"><code
class="notranslate">32957cd</code></a> feat: support TS syntax in <code
class="notranslate">max-params</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19557">#19557</a>)
(Nitin Kumar)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="5687ce7055"><code
class="notranslate">5687ce7</code></a> fix: correct mismatched removed
rules (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19734">#19734</a>)
(루밀LuMir)</li>
<li>
<a
href="dc5ed337fd"><code
class="notranslate">dc5ed33</code></a> fix: correct types and tighten
type definitions in <code class="notranslate">SourceCode</code> class
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19731">#19731</a>)
(루밀LuMir)</li>
<li>
<a
href="de1b5deba0"><code
class="notranslate">de1b5de</code></a> fix: correct <code
class="notranslate">service</code> property name in <code
class="notranslate">Linter.ESLintParseResult</code> type (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19713">#19713</a>)
(Francesco Trotta)</li>
<li>
<a
href="60c3e2cf92"><code
class="notranslate">60c3e2c</code></a> fix: sort keys in
eslint-suppressions.json to avoid git churn (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19711">#19711</a>)
(Ron Waldon-Howe)</li>
<li>
<a
href="9da90ca3c1"><code
class="notranslate">9da90ca</code></a> fix: add <code
class="notranslate">allowReserved</code> to <code
class="notranslate">Linter.ParserOptions</code> type (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19710">#19710</a>)
(Francesco Trotta)</li>
<li>
<a
href="fbb8be9256"><code
class="notranslate">fbb8be9</code></a> fix: add <code
class="notranslate">info</code> to <code
class="notranslate">ESLint.DeprecatedRuleUse</code> type (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19701">#19701</a>)
(Francesco Trotta)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="25de55055d"><code
class="notranslate">25de550</code></a> docs: Update description of
frozen rules to mention TypeScript (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19736">#19736</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="bd5def66d1"><code
class="notranslate">bd5def6</code></a> docs: Clean up configuration
files docs (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19735">#19735</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="4d0c60d073"><code
class="notranslate">4d0c60d</code></a> docs: Add Neovim to editor
integrations (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19729">#19729</a>)
(Maria José Solano)</li>
<li>
<a
href="71317ebeaf"><code
class="notranslate">71317eb</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="4c289e685e"><code
class="notranslate">4c289e6</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="f0f0d46ab2"><code
class="notranslate">f0f0d46</code></a> docs: clarify that unused
suppressions cause non-zero exit code (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19698">#19698</a>)
(Milos Djermanovic)</li>
<li>
<a
href="8ed32734cc"><code
class="notranslate">8ed3273</code></a> docs: fix internal usages of
<code class="notranslate">ConfigData</code> type (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19688">#19688</a>)
(Francesco Trotta)</li>
<li>
<a
href="eb316a83a4"><code
class="notranslate">eb316a8</code></a> docs: add <code
class="notranslate">fmt</code> and <code
class="notranslate">check</code> sections to <code
class="notranslate">Package.json Conventions</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19686">#19686</a>)
(루밀LuMir)</li>
<li>
<a
href="a3a2559248"><code
class="notranslate">a3a2559</code></a> docs: fix wording in Combine
Configs (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19685">#19685</a>)
(Milos Djermanovic)</li>
<li>
<a
href="c8d17e11dc"><code
class="notranslate">c8d17e1</code></a> docs: Update README (GitHub
Actions Bot)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="f8f1560de6"><code
class="notranslate">f8f1560</code></a> chore: upgrade @eslint/js@9.27.0
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19739">#19739</a>)
(Milos Djermanovic)</li>
<li>
<a
href="ecaef7351f"><code
class="notranslate">ecaef73</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="596fdc6204"><code
class="notranslate">596fdc6</code></a> chore: update dependency
@arethetypeswrong/cli to ^0.18.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19732">#19732</a>)
(renovate[bot])</li>
<li>
<a
href="f791da0401"><code
class="notranslate">f791da0</code></a> chore: remove unbalanced curly
brace from <code class="notranslate">.editorconfig</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19730">#19730</a>)
(Maria José Solano)</li>
<li>
<a
href="e86edee091"><code
class="notranslate">e86edee</code></a> refactor: Consolidate Config
helpers (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19675">#19675</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="cf3635299e"><code
class="notranslate">cf36352</code></a> chore: remove shared types (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19718">#19718</a>)
(Francesco Trotta)</li>
<li>
<a
href="f60f276497"><code
class="notranslate">f60f276</code></a> refactor: Easier RuleContext
creation (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19709">#19709</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="58a171e8f0"><code
class="notranslate">58a171e</code></a> chore: update dependency
@eslint/plugin-kit to ^0.3.1 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19712">#19712</a>)
(renovate[bot])</li>
<li>
<a
href="3a075a29cf"><code
class="notranslate">3a075a2</code></a> chore: update dependency
@eslint/core to ^0.14.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19715">#19715</a>)
(renovate[bot])</li>
<li>
<a
href="44bac9d15c"><code
class="notranslate">44bac9d</code></a> ci: run tests in Node.js 24 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19702">#19702</a>)
(Francesco Trotta)</li>
<li>
<a
href="35304dd2b0"><code
class="notranslate">35304dd</code></a> chore: add missing <code
class="notranslate">funding</code> field to packages (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19684">#19684</a>)
(루밀LuMir)</li>
<li>
<a
href="f305beb82c"><code
class="notranslate">f305beb</code></a> test: mock <code
class="notranslate">process.emitWarning</code> to prevent output
disruption (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19687">#19687</a>)
(Francesco Trotta)</li>
</ul></blockquote>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.26.0">9.26.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="e9754e7433"><code
class="notranslate">e9754e7</code></a> feat: add reportGlobalThis to
no-shadow-restricted-names (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19670">#19670</a>)
(sethamus)</li>
<li>
<a
href="0fa2b7a366"><code
class="notranslate">0fa2b7a</code></a> feat: add suggestions for <code
class="notranslate">eqeqeq</code> rule (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19640">#19640</a>)
(Nitin Kumar)</li>
<li>
<a
href="dcbdcc9c6b"><code
class="notranslate">dcbdcc9</code></a> feat: Add MCP server (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19592">#19592</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="2dfd83ef4e"><code
class="notranslate">2dfd83e</code></a> feat: add <code
class="notranslate">ignoreDirectives</code> option in <code
class="notranslate">no-unused-expressions</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19645">#19645</a>)
(sethamus)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="96e84de55a"><code
class="notranslate">96e84de</code></a> fix: check cache file existence
before deletion (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19648">#19648</a>)
(sethamus)</li>
<li>
<a
href="d683aebc8e"><code
class="notranslate">d683aeb</code></a> fix: don't crash on tests with
circular references in <code class="notranslate">RuleTester</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19664">#19664</a>)
(Milos Djermanovic)</li>
<li>
<a
href="9736d5d158"><code
class="notranslate">9736d5d</code></a> fix: add <code
class="notranslate">namespace</code> to <code
class="notranslate">Plugin.meta</code> type (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19661">#19661</a>)
(Milos Djermanovic)</li>
<li>
<a
href="17bae69e02"><code
class="notranslate">17bae69</code></a> fix: update <code
class="notranslate">RuleTester.run()</code> type (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19634">#19634</a>)
(Nitin Kumar)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="dd98d63f09"><code
class="notranslate">dd98d63</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="c25e858d2d"><code
class="notranslate">c25e858</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="b2397e9bef"><code
class="notranslate">b2397e9</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="addd0a6a62"><code
class="notranslate">addd0a6</code></a> docs: fix formatting of unordered
lists in Markdown (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19660">#19660</a>)
(Milos Djermanovic)</li>
<li>
<a
href="a21b38db02"><code
class="notranslate">a21b38d</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="c0721a7f34"><code
class="notranslate">c0721a7</code></a> docs: fix double space in command
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19657">#19657</a>)
(CamWass)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="5b247c859f"><code
class="notranslate">5b247c8</code></a> chore: upgrade to <code
class="notranslate">@eslint/js@9.26.0</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19681">#19681</a>)
(Francesco Trotta)</li>
<li>
<a
href="d6fa4ac031"><code
class="notranslate">d6fa4ac</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="09586905be"><code
class="notranslate">0958690</code></a> chore: disambiguate internal
types <code class="notranslate">LanguageOptions</code> and <code
class="notranslate">Rule</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19669">#19669</a>)
(Francesco Trotta)</li>
<li>
<a
href="f1c858e3c1"><code
class="notranslate">f1c858e</code></a> chore: fix internal type
references to <code class="notranslate">Plugin</code> and <code
class="notranslate">Rule</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19665">#19665</a>)
(Francesco Trotta)</li>
<li>
<a
href="40dd2998ce"><code
class="notranslate">40dd299</code></a> refactor: One-shot ESQuery
selector analysis (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19652">#19652</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="1cfd702422"><code
class="notranslate">1cfd702</code></a> chore: update dependency
@eslint/json to ^0.12.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19656">#19656</a>)
(renovate[bot])</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="3ed4b3652d...b9080cf28d">See
the full diff on Github</a>. The new version differs by 58 commits:</p>
<ul>
<li><a
href="b9080cf28d"><code>9.27.0</code></a></li>
<li><a
href="b7a5c66129"><code>Build:
changelog update for 9.27.0</code></a></li>
<li><a
href="f8f1560de6"><code>chore:
upgrade @eslint/js@9.27.0 (#19739)</code></a></li>
<li><a
href="ecaef7351f"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="25de55055d"><code>docs:
Update description of frozen rules to mention TypeScript
(#19736)</code></a></li>
<li><a
href="bd5def66d1"><code>docs:
Clean up configuration files docs (#19735)</code></a></li>
<li><a
href="d71e37f450"><code>feat:
Allow flags to be set in ESLINT_FLAGS env variable
(#19717)</code></a></li>
<li><a
href="5687ce7055"><code>fix:
correct mismatched removed rules (#19734)</code></a></li>
<li><a
href="596fdc6204"><code>chore:
update dependency @arethetypeswrong/cli to ^0.18.0
(#19732)</code></a></li>
<li><a
href="ba456e000e"><code>feat:
Externalize MCP server (#19699)</code></a></li>
<li><a
href="dc5ed337fd"><code>fix:
correct types and tighten type definitions in `SourceCode` class
(#19731)</code></a></li>
<li><a
href="4d0c60d073"><code>docs:
Add Neovim to editor integrations (#19729)</code></a></li>
<li><a
href="f791da0401"><code>chore:
remove unbalanced curly brace from `.editorconfig`
(#19730)</code></a></li>
<li><a
href="e86edee091"><code>refactor:
Consolidate Config helpers (#19675)</code></a></li>
<li><a
href="07c1a7e839"><code>feat:
add `allowRegexCharacters` to `no-useless-escape`
(#19705)</code></a></li>
<li><a
href="cf3635299e"><code>chore:
remove shared types (#19718)</code></a></li>
<li><a
href="f60f276497"><code>refactor:
Easier RuleContext creation (#19709)</code></a></li>
<li><a
href="71317ebeaf"><code>docs:
Update README</code></a></li>
<li><a
href="de1b5deba0"><code>fix:
correct `service` property name in `Linter.ESLintParseResult` type
(#19713)</code></a></li>
<li><a
href="58a171e8f0"><code>chore:
update dependency @eslint/plugin-kit to ^0.3.1 (#19712)</code></a></li>
<li><a
href="3a075a29cf"><code>chore:
update dependency @eslint/core to ^0.14.0 (#19715)</code></a></li>
<li><a
href="60c3e2cf92"><code>fix:
sort keys in eslint-suppressions.json to avoid git churn
(#19711)</code></a></li>
<li><a
href="4c289e685e"><code>docs:
Update README</code></a></li>
<li><a
href="9da90ca3c1"><code>fix:
add `allowReserved` to `Linter.ParserOptions` type
(#19710)</code></a></li>
<li><a
href="7bc6c71ca3"><code>feat:
add no-unassigned-vars rule (#19618)</code></a></li>
<li><a
href="ee40364297"><code>feat:
convert no-array-constructor suggestions to autofixes
(#19621)</code></a></li>
<li><a
href="fbb8be9256"><code>fix:
add `info` to `ESLint.DeprecatedRuleUse` type (#19701)</code></a></li>
<li><a
href="f0f0d46ab2"><code>docs:
clarify that unused suppressions cause non-zero exit code
(#19698)</code></a></li>
<li><a
href="44bac9d15c"><code>ci:
run tests in Node.js 24 (#19702)</code></a></li>
<li><a
href="32957cde72"><code>feat:
support TS syntax in `max-params` (#19557)</code></a></li>
<li><a
href="35304dd2b0"><code>chore:
add missing `funding` field to packages (#19684)</code></a></li>
<li><a
href="8ed32734cc"><code>docs:
fix internal usages of `ConfigData` type (#19688)</code></a></li>
<li><a
href="f305beb82c"><code>test:
mock `process.emitWarning` to prevent output disruption
(#19687)</code></a></li>
<li><a
href="eb316a83a4"><code>docs:
add `fmt` and `check` sections to `Package.json Conventions`
(#19686)</code></a></li>
<li><a
href="a3a2559248"><code>docs:
fix wording in Combine Configs (#19685)</code></a></li>
<li><a
href="c8d17e11dc"><code>docs:
Update README</code></a></li>
<li><a
href="8bbabc4691"><code>9.26.0</code></a></li>
<li><a
href="16f5ff7991"><code>Build:
changelog update for 9.26.0</code></a></li>
<li><a
href="5b247c859f"><code>chore:
upgrade to `@eslint/js@9.26.0` (#19681)</code></a></li>
<li><a
href="d6fa4ac031"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="e9754e7433"><code>feat:
add reportGlobalThis to no-shadow-restricted-names
(#19670)</code></a></li>
<li><a
href="0fa2b7a366"><code>feat:
add suggestions for `eqeqeq` rule (#19640)</code></a></li>
<li><a
href="dd98d63f09"><code>docs:
Update README</code></a></li>
<li><a
href="96e84de55a"><code>fix:
check cache file existence before deletion (#19648)</code></a></li>
<li><a
href="c25e858d2d"><code>docs:
Update README</code></a></li>
<li><a
href="09586905be"><code>chore:
disambiguate internal types `LanguageOptions` and `Rule`
(#19669)</code></a></li>
<li><a
href="dcbdcc9c6b"><code>feat:
Add MCP server (#19592)</code></a></li>
<li><a
href="b2397e9bef"><code>docs:
Update README</code></a></li>
<li><a
href="d683aebc8e"><code>fix:
don&#39;t crash on tests with circular references in `RuleTester`
(#19664)</code></a></li>
<li><a
href="f1c858e3c1"><code>chore:
fix internal type references to `Plugin` and `Rule`
(#19665)</code></a></li>
<li><a
href="9736d5d158"><code>fix:
add `namespace` to `Plugin.meta` type (#19661)</code></a></li>
<li><a
href="40dd2998ce"><code>refactor:
One-shot ESQuery selector analysis (#19652)</code></a></li>
<li><a
href="addd0a6a62"><code>docs:
fix formatting of unordered lists in Markdown (#19660)</code></a></li>
<li><a
href="a21b38db02"><code>docs:
Update README</code></a></li>
<li><a
href="c0721a7f34"><code>docs:
fix double space in command (#19657)</code></a></li>
<li><a
href="1cfd702422"><code>chore:
update dependency @eslint/json to ^0.12.0 (#19656)</code></a></li>
<li><a
href="2dfd83ef4e"><code>feat:
add `ignoreDirectives` option in `no-unused-expressions`
(#19645)</code></a></li>
<li><a
href="17bae69e02"><code>fix:
update `RuleTester.run()` type (#19634)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-05-23 17:57:32 -04:00
depfu[bot]
8fcc63331c
Update @napi-rs/wasm-runtime 0.2.9 → 0.2.10 (minor) (#18127)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​napi-rs/wasm-runtime (0.2.9 → 0.2.10) ·
[Repo](https://github.com/napi-rs/napi-rs)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-05-23 17:52:54 -04:00
Robin Malfait
884f02c46b
Fix "Cannot read properties of undefined" crash on malformed arbitrary value (#18133)
This PR fixes a crash when an arbitrary value was malformed and crashed
the build.

If you have a utility like `[--btn-border:var(--color-maroon)/90)]`
which is malformed, it will crash the build. It might not be easy to
spot but the easy is the additional `)` after the `90`.

The reason this crashes is because we parse the value
`var(--color-maroon)/90)` and when we see `)` we assume it's the end of
a "function" which also assumes it was preceded by a `(`. This is not
the case and we crash.

This PR fixes that by not assuming the parsed object is available and
uses `?` to be safe and only access `nodes` if it's available.

I'm actually not 100% sure what the best solution is in this scenario
because these candidates could (and will) be returned from Oxide so even
if we throw a more descriptive error, it will still crash the build and
you might not even have control over the candidate.

This candidate will now eventually generate the following CSS:
```css
.\[--btn-border\:var\(--color-maroon\)\/90\)\] {
  --btn-border: var(--color-maroon) / ;
}
```

Which still looks odd, but even Lightning CSS doesn't throw an error in
this case (because it's a CSS variable definition), so I think it's the
best we can do. If you open your devtools you will see the weird values,
so it's still debug-able.

<img width="359" alt="image"
src="https://github.com/user-attachments/assets/2eb48662-64de-4417-a2da-1577bf9075b5"
/>

Fixes: #17064

## Test plan

Manually tested the candidate that crashed it, and after the change
generated the above CSS. Then used it in JSFiddle to proof it's fixed
now. https://jsfiddle.net/z850ykew/

Couldn't use Tailwind Play because the candidate will cause a crash
there as well 😅
2025-05-23 19:59:02 +00:00
Robin Malfait
9cb38993d2
Upgrade deprecated order-none to order-0 (#18126)
This PR adds an initial version for deprecated utilities. Right now it's
hardcoded to just the `order-none` utility.

This means that `order-0` and `order-[0]` will not be migrated to
`order-none` anymore. We did that automatically because we prefer named
utilities over bare values and arbitrary values.

With this PR, `order-none` is ignored.

Similarly, `order-none` will be migrated to `order-0` instead (defined
in a separate migration for deprecated values). Made it a new migration
instead of using the legacy migration because there all utilities still
exist, but are defined differently (e.g.: `shadow`, `shadow-sm`,
`shadow-xs`).

This PR is also an initial version, it doesn't add any form of
`deprecated` flag or feature on a per-utility implementation basis. This
therefor has the side effect that if you have a custom `order-none`
defined, that it will also be ignored during migrations.


## Test plan

1. Added tests to ensure the `order-0` is not migrated to `order-none`
2. Added tests to ensure `order-none` is migrated to `order-0` (if it's
safe to do so, the signature is still computed to ensure the output is
the same).
3. Ran this on the Tailwind Plus codebase and ensured that `order-0` is
not migrated to `order-none` and that `order-none` is migrated to
`order-0`.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-05-23 15:21:21 +00:00
Álvaro Mondéjar Rubio
ed3cecd39d
Support Leptos class: attributes (#18093)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

Fixes #18092

## Summary

Using the Svelte preprocessor for Rust files we can support Leptos
`class:` attributes syntax.

## Test plan

```
pnpm t
```

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-05-23 10:15:33 +00:00
depfu[bot]
24ed64ebb3
Update @types/react 19.1.2 → 19.1.4 (patch) (#18085)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react (19.1.2 → 19.1.4) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-05-19 16:07:59 -04:00
depfu[bot]
7013ca37ed
Update semver 7.7.1 → 7.7.2 (patch) (#18084)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ semver (7.7.1 → 7.7.2) ·
[Repo](https://github.com/npm/node-semver) ·
[Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/npm/node-semver/releases/tag/v7.7.2">7.7.2</a></h4>

<blockquote><h2 dir="auto">
<a
href="https://bounce.depfu.com/github.com/npm/node-semver/compare/v7.7.1...v7.7.2">7.7.2</a>
(2025-05-12)</h2>
<h3 dir="auto">Bug Fixes</h3>
<ul dir="auto">
<li>
<a
href="fcafb61ed5"><code
class="notranslate">fcafb61</code></a> <a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/780">#780</a>
add missing <code class="notranslate">'use strict'</code> directives (<a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/780">#780</a>)
(<a href="https://bounce.depfu.com/github.com/Fdawgs">@Fdawgs</a>)</li>
<li>
<a
href="c99f336fa3"><code
class="notranslate">c99f336</code></a> <a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/781">#781</a>
prerelease identifier starting with digits (<a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/781">#781</a>)
(<a
href="https://bounce.depfu.com/github.com/mbtools">@mbtools</a>)</li>
</ul>
<h3 dir="auto">Chores</h3>
<ul dir="auto">
<li>
<a
href="c760403b93"><code
class="notranslate">c760403</code></a> <a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/784">#784</a>
template-oss-apply for workflow permissions (<a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/784">#784</a>)
(<a
href="https://bounce.depfu.com/github.com/wraithgar">@wraithgar</a>)</li>
<li>
<a
href="2677f2a883"><code
class="notranslate">2677f2a</code></a> <a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/778">#778</a>
bump @npmcli/template-oss from 4.23.6 to 4.24.3 (<a
href="https://bounce.depfu.com/github.com/npm/node-semver/pull/778">#778</a>)
(<a
href="https://bounce.depfu.com/github.com/dependabot">@dependabot</a>[bot],
<a
href="https://bounce.depfu.com/github.com/npm-cli-bot">@npm-cli-bot</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="30c438bb46...281055e771">See
the full diff on Github</a>. The new version differs by 6 commits:</p>
<ul>
<li><a
href="281055e771"><code>chore:
release 7.7.2 (#783)</code></a></li>
<li><a
href="fcafb61ed5"><code>fix:
add missing `&#39;use strict&#39;` directives (#780)</code></a></li>
<li><a
href="c760403b93"><code>chore:
template-oss-apply for workflow permissions (#784)</code></a></li>
<li><a
href="c99f336fa3"><code>fix:
prerelease identifier starting with digits (#781)</code></a></li>
<li><a
href="2677f2a883"><code>chore:
bump @npmcli/template-oss from 4.23.6 to 4.24.3 (#778)</code></a></li>
<li><a
href="0b98655dbc"><code>chore:
bump @npmcli/template-oss from 4.23.4 to 4.23.6 (#760)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-05-19 15:19:43 -04:00
Jordan Pittman
9df5ba78bf
Fix PostCSS crash when using toJSON() (#18083)
When I added source maps to PostCSS I mistakenly assumed that `.source`
on a node could be `undefined`. The comment above the property in
PostCSS says that `source` can be `undefined` but this is a commentary
on the value upon **access** not its expected value on **write**:

```ts
declare abstract class Node_ {
  /**
   * …
   *
   * The nodes that are created manually using the public APIs
   * provided by PostCSS will have `source` undefined and
   * will be absent in the source map.
   *
   * …
   */
  source?: Node.Source
}
```

Rather, what these types mean is that *if the property exists* it must
be defined. But otherwise the property can be missing if a node has no
source location metadata. This generally wasn't a problem with the
string-returning APIs but the `toJSON()` API in PostCSS expects that
`source` is defined if present. This caused a crash because our license
comment doesn't have a source location.

I've addressed this by deleting the `source` property from the node if
source location data is not available.

Fixes #18082

ref https://github.com/parcel-bundler/parcel/issues/10161
2025-05-19 11:28:09 -04:00
Robin Malfait
a42251cc29
Improve performance of upgrade tool (#18068)
This PR improves the performance of the upgrade tool due to a regression
introduced by https://github.com/tailwindlabs/tailwindcss/pull/18057

Essentially, we had to make sure that we are not in `<style>…</style>`
tags because we don't want to migrate declarations in there such as
`flex-shrink: 0;`

The issue with this approach is that we checked _before_ the candidate
if a `<style` cold be found and if we found an `</style>` tag after the
candidate.

We would basically do this check for every candidate that matches.

Running this on our Tailwind UI codebase, this resulted in a bit of a
slowdown:

```diff
- Before: ~13s
+  After: ~5m 39s
```

... quite the difference.

This is because we have a snapshot file that contains ~650k lines of
code. Looking for `<style>` and `</style>` tags in a file that large is
expensive, especially if we do it a lot.

I ran some numbers and that file contains ~1.8 million candidates.

Anyway, this PR fixes that by doing a few things:

1. We will compute the `<style>` and `</style>` tag positions only once
per file and cache it. This allows us to re-use this work for every
candidate that needs it.
2. We track the positions, which means that we can simply check if a
candidate's location is within any of 2 start and end tags. If so, we
skip it.

Running the numbers now gets us to:

```diff
- Before: ~5m 39s
+  After: ~9s
```

Much better!

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-05-19 10:41:40 -04:00
Robin Malfait
71fb9cdf59
Improve @tailwindcss/upgrade and pnpm workspaces support (#18065)
This PR fixes an issue where an error such as:

<img width="1702" alt="image"
src="https://github.com/user-attachments/assets/4e6f75c7-3182-4497-939e-96cff08c55ae"
/>

Will be thrown during the upgrade process. This can happen when you are
using `pnpm` and your CSS file includes a `@import "tailwindcss";`. In
this scenario, `tailwindcss` will be loaded from a shared `.pnpm` folder
outside of the current working directory.

In this case, we are also not interested in migrating _that_ file, but
we also don't want the upgrade process to just crash.

I didn't see an option to ignore errors like this, so wrapped it in a
try/catch instead.

It also fixes another issue where if you are using a pnpm workspace and
run the upgrade tool from the root, then it throws you an error that you
cannot add dependencies to the workspace root unless `-w` or
`--workspace-root` flags are passed.

For this, we disable the check entirely using the
`--ignore-workspace-root-check` flag. If we always used the
`--workspace-root` flag, then the dependencies would always be added to
the root, regardless of where you are running the script from which is
not what we want.

## Test plan

Before:

<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/78246876-3eb6-4539-a557-d3d366f1b3a3"
/>

After:

<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/a65e4421-d7c5-4d83-b35d-934708543e25"
/>

Before:
 
<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/53772661-2c4a-4212-84d9-a556a0ad320f"
/>

After:

<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/5bfaf20e-34b8-44fd-9b59-e72d36738879"
/>
2025-05-19 12:47:08 +02:00
Robin Malfait
c7d368b3c4
Do not migrate declarations in <style> blocks (#18057)
This PR improves the upgrade tool by making sure that we don't migrate
CSS declarations in `<style>…</style>` blocks.

We do this by making sure that:

1. We detect a declaration, the current heuristic is that the candidate
is:
   - Preceded by whitespace
   - Followed by a colon and whitespace

   ```html
   <style>
   .foo {
       flex-shrink: 0;
      ^           ^^
   }
   </style>
   ```

2. We are in a `<style>…</style>` block

   ```html
   <style>
   ^^^^^^
   .foo {
       flex-shrink: 0;
   }
   </style>
   ^^^^^^^^
   ```

The reason we have these 2 checks is because just relying on the first
heuristic alone, also means that we will not be migrating keys in JS
objects, because they typically follow the same structure:

```js
let classes = {
 flex: 0,
^    ^^
}
```

Another important thing to note is that we can't just ignore anything in
between `<style>…</style>` blocks, because you could still be using
`@apply` that we _do_ want to migrate.

Last but not least, the first heuristics is not perfect either. If you
are writing minified CSS then this will likely fail if there is no
whitespace around the candidate.

But my current assumption is that nobody should be writing minified CSS,
and minified CSS will very likely be generated and gitignored. In either
situation, replacements in minified CSS will not be any worse than it is
today.

I'm open to suggestions for better heuristics.

## Test plan

1. Added an integration test that verifies that we do migrate `@apply`
and don't migrate the `flex-shrink: 0;` declaration.


Fixes: #17975
2025-05-16 12:53:34 +02:00
depfu[bot]
d69604e202
Update all of nextjs 15.3.1 → 15.3.2 (patch) (#18046) 2025-05-15 18:12:52 +02:00
Philipp Spiess
74e084ad27
Prepare v4.1.7 release (#18040)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-05-15 15:31:18 +02:00
Robin Malfait
bf591febf3
Fix missing extracted classes containing . in Clojure (#18038)
This PR fixes an issue in the Clojure pre-processor where candidates
including `.` characters were not extracted correctly.

The solution here is to only replace the `.` with a ` ` when the `.` is
not surrounded by numbers. This means that:


```
:.foo.bar
```
Becomes
```
: foo bar
```

But
```
:.gap-1.5.flex
```

Becomes
```
: gap-1.5 flex
```

This way the `gap-1.5` is correctly extracted.

## Test plan

1. Added a test for this case
2. Tested this in the extractor tool as well. Notice how the `gap-1.5`
is correctly extracted here.

<img width="1247" alt="image"
src="https://github.com/user-attachments/assets/f5dd2600-5c5e-4ad8-88af-4e5be44340f5"
/>


Fixes: 17760
2025-05-15 14:50:03 +02:00
Robin Malfait
1ada8e0f22
Make candidate template migrations faster (#18025)
This PR makes the migrations for templates much faster. To make this
work, I also had to move things around a bit (so you might want to check
this PR commit by commit). I also solved an issue by restructuring the
code.

### Performance

For starters, we barely applied any caching when migrating candidates
from α to β. The problem with this is that in big projects the same
candidates will appear _everywhere_, so caching is going to be useful
here.

One of the reasons why we didn't do any caching is that some migrations
were checking if a migration is actually safe to do. To do this, we were
checking the `location` (the location of the candidate in the template).
Since this location is unique for each template, caching was not
possible.

So the first order of business was to hoist the `isSafeMigration` check
up as the very first thing we do in the migration.

If we do this first, then the only remaining code relies on the
`DesignSystem`, `UserConfig` and `rawCandidate`.

In a project, the `DesignSystem` and `UserConfig` will be the same
during the migration, only the `rawCandidate` will be different which
means that we can move all this logic in a good old `DefaultMap` and
cache the heck out of it.

Running the numbers on our Tailwind Plus repo, this results in:
```
    Total seen candidates: 2 211 844
Total migrated candidates: 7 775 
               Cache hits: 1 575 700
```

That's a lot of work we _don't_ have to do. Looking at the timings, the
template migration step goes from ~45s to ~10s because of this.

Another big benefit of this is that this makes migrations _actually_
safe. Before we were checking if a migration was safe to do in specific
migrations. But other migrations were still printing the candidate which
could still result in an unsafe migration.

For example when migrating the `blur` and the `shadow` classes, the
`isSafeMigration` was used. But if the input was `!flex` then the safety
check wasn't even checked in this specific migration.

### Safe migrations

Also made some changes to the `isSafeMigration` logic itself. We used to
start by checking the location, but thinking about the problem again,
the actual big problem we were running into is classes that are short
like `blur`, and `shadow` because they could be used in other contexts
than a Tailwind CSS class.

Inverting this logic means that more specific Tailwind CSS classes will
very likely _not_ cause any issues at all.

For example:
- If you have variants: `hover:focus:flex`
- If you have arbitrary properties: `[color:red]`
- If you have arbitrary values: `bg-[red]`
- If you have a modifier: `bg-red-500/50`
- If you have a `-` in the name: `bg-red-500`

Even better if we can't parse a candidate at all, we can skip the
migrations all together.

This brings us to the issue in #17974, one of the issues was already
solved by just hoisting the `isSafeMigration`. But to make the issue was
completely solved I also made sure that in Vue attributes like
`:active="…"` are also considered unsafe (note: `:class` is allowed).

Last but not least, in case of the `!duration` that got replaced with
`duration!` was solved by verifying that the candidate actually produces
valid CSS. We can compute the signature for this class.

The reason this wasn't thrown away earlier is because we can correctly
parse `duration` but `duration` on its own doesn't exist,
`duration-<number>` does exist as a functional utility which is why it
parsed in the first place.

Fixes: #17974

## Test plan

1. Ran the tool on our Tailwind UI Templates repo to compare the new
output with the "old" behavior and there were no differences in output.
2. Ran the tool on our Tailwind Plus repo, and the template migration
step went from ~45s to ~10s.
3. Added additional tests to verify the issues in #17974 are fixed.


[ci-all] let's run this on all CI platforms...
2025-05-15 11:17:39 +00:00
Philipp Spiess
6fb98d2f40
Upgrade lightningcss to 1.30.1 (#18037) 2025-05-15 13:16:02 +02:00
Philipp Spiess
f3157cd9a6
Ignore custom variants with :merge(…) selectors (#18020)
Closes #15617

## Summary

This PR ignores `addVariant(…)` legacy JS plugin calls for variants that
are using the [`:merge(…)`
selector](https://v3.tailwindcss.com/docs/plugins#parent-and-sibling-states)
for parent and sibling states. We can ignore these now because in v4,
`group-*` and `peer-*` variants _compound automatically_ and you don't
have to define them anymore.

## Test plan

Added a unit test to ensure that the `optional` variant example from the
v3 docs work as expected.
2025-05-15 13:00:00 +02:00
Philipp Spiess
e57a2f5a3a
Change casing of utilities with named values to kebab-case to match u… (#18017)
Fixes #16156

## Summary

This PR adds a new 3 -> 4 template migration that changes the casing of
in both utility values and modifier values from camelCase to kebab-case
to match the updated CSS variable names.

## Test plan

- Added integration test, see the diff in the PR.
2025-05-14 14:51:51 +02:00
Philipp Spiess
4db711d1e4
Fix -rotate-* with arbitary values (#18014)
Fixes #18013

## Test plan

See updated unit tests
2025-05-14 11:40:37 +02:00
Robin Malfait
498f9ff003
Migrate bare values to named values (#18000)
This PR improves the upgrade tool by also migrating bare values to named
values defined in the `@theme`.

Recently we shipped some updates dat allowed us to migrate arbitrary
values (with square brackets), but we didn't migrate bare values yet.

That means that in this example:
```html
<div class="aspect-[16/9]"></div>
<div class="aspect-16/9"></div>
```

We migrated this to:
```html
<div class="aspect-video"></div>
<div class="aspect-16/9"></div>
```

With this change, we will also try and migrate the bare value to a named
value. So this example:
```html
<div class="aspect-[16/9]"></div>
<div class="aspect-16/9"></div>
```

Now becomes:
```html
<div class="aspect-video"></div>
<div class="aspect-video"></div>
```

## Test plan

1. Added unit tests for the new functionality.
2. Ran this on a local project


Before:
<img width="432" alt="image"
src="https://github.com/user-attachments/assets/ce1adfbd-7be1-4062-bea5-66368f748e44"
/>

After:
<img width="382" alt="image"
src="https://github.com/user-attachments/assets/a385c94c-4e4c-4e1c-ac73-680c56ac4081"
/>
2025-05-13 17:35:11 +02:00
Philipp Spiess
ef2e6c71fe
Upgrade: Migrate outline class (#17996)
This PR adds a migration from `outline` to `outline-solid` for the v3 ->
v4 upgrade tool.

## Test plan

- Added integration test
2025-05-13 14:20:40 +02:00
Philipp Spiess
4fba87bc90
Upgrade lightningcss to 1.30.0 (#17979) 2025-05-12 15:49:18 +02:00
Philipp Spiess
ba944ca3b1
Upgrade bun to 1.2.13 (#17981) 2025-05-12 15:17:19 +02:00
Tommy D. Rossi
5688f0a7ae
Add ignore pattern for node_modules in globby search in upgrade cli (#17969)
When passing `gitignore: true` to globby it will start a search for all
.gitignore files, this initial search includes node_modules making it
hang forever for large monorepos with many files inside node_modules
2025-05-12 11:09:40 +00:00
depfu[bot]
19e2b29c68
Update @types/react-dom 19.1.2 → 19.1.3 (patch) (#17976) 2025-05-12 11:02:57 +02:00
depfu[bot]
0d975f5f06
Update dedent 1.5.3 → 1.6.0 (minor) (#17965)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ dedent (1.5.3 → 1.6.0) · [Repo](https://github.com/dmnd/dedent)
· [Changelog](https://github.com/dmnd/dedent/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/dmnd/dedent/releases/tag/v1.6.0">1.6.0</a></h4>

<blockquote><h2 dir="auto">What's Changed</h2>
<ul dir="auto">
<li>feat: add <code class="notranslate">trimWhitespace</code> option by
<a href="https://bounce.depfu.com/github.com/43081j">@43081j</a> in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/97">#97</a>
</li>
</ul>
<h2 dir="auto">New Contributors</h2>
<ul dir="auto">
<li>
<a href="https://bounce.depfu.com/github.com/43081j">@43081j</a> made
their first contribution in <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/pull/97">#97</a>
</li>
</ul>
<p dir="auto"><strong>Full Changelog</strong>: <a
href="https://bounce.depfu.com/github.com/dmnd/dedent/compare/v1.5.3...v1.6.0"><tt>v1.5.3...v1.6.0</tt></a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="90644fe0be...ab2ce25762">See
the full diff on Github</a>. The new version differs by 2 commits:</p>
<ul>
<li><a
href="ab2ce25762"><code>1.6.0
(#98)</code></a></li>
<li><a
href="86902f7c97"><code>feat:
add `trimWhitespace` option (#97)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-05-10 13:21:48 +02:00
Jordan Pittman
f0986ce127
Prevent duplicate suggestions when using @theme and @utility together (#17675)
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1313

Right now given this CSS:
```css
@theme reference {
  --text-header: 1.5rem;
}

@utility text-header {
  text-transform: uppercase;
}
```

You'll see two entries for `text-header` in IntelliSense completions but
we only want you to see one. This PR solves this by merging their
modifier lists and de-duping by class name.
2025-05-09 20:12:43 +00:00
Robin Malfait
3386049b7b
Fix upgrade error when using @import … source(…) (#17963)
This PR fixes an issue when running the upgrade tool and if any of the
CSS files has an import that looks like this:

```css
@import "tailwindcss" source("…");
```

This was trying to resolve `tailwindcss" source("…` instead of
`tailwindcss`.

This PR fixes that.

## Test plan

1. Ran it locally on a project

Before:

<img width="1158" alt="image"
src="https://github.com/user-attachments/assets/09bf5d69-797c-4330-ade1-edc213f7ce5c"
/>

After:

<img width="1029" alt="image"
src="https://github.com/user-attachments/assets/d1c9e194-30e2-4564-83c5-d9a259a67e90"
/>
2025-05-09 20:04:48 +00:00
Robin Malfait
737994b7aa
Allow _ before numbers during candidate extraction (#17961)
This PR fixes a bug where a class like `header_1` wasn't properly
extracted because we didn't allow an `_` before a number.

This PR fixes that by allowing an `_` before a number.

Fixes: #17960


## Test plan

1. Added a test to verify this works
2. Existing tests work

Used the visualizer tool for this to verify that the `header_1` class is
being extracted:
<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/fdc21602-0e2b-4e4e-92a1-19c4f4f5393f"
/>
2025-05-09 16:29:28 +00:00
Robin Malfait
2d139984da
Prepare v4.1.6 release (#17951)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-05-09 13:39:18 +00:00
Philipp Spiess
47bb007eae
Download platform specific package if optionalDependencies are skipped (#17929)
Closes #15806

This PR adds a new `postinstall` script to `@tailwindcss/oxide` that
will attempt to download the platform-specific optional dependency to
avoid issues when the package manager does not do that automatically
(see #15806). The implementation for this is fairly simple: The npm
package is downloaded from the official npm servers and extracted into
the `node_modules` directory of the `@tailwidncss/oxide` installation.

## Test plan 

Since we still lack a solid repro of #15806, the way I tested this
change was to manually remove all automatically-installed optional
dependencies and then running the postinstall script manually. The
script then downloads the right version package which makes the import
to `@tailwidncss/oxide` work. An appropriate integration test was added
too.

I furthermore also validated that:

- This works across CI platforms [ci-all]
- The postinstall script bails out when running `pnpm install` in the
dev setup. This is necessary since doing the initial install won't have
any binary dependencies yet so it would download invalid versions from
npm (as the version numbers locally refer to the last released version).
We can safely bail out here though since this was never an issue with
local development.
- The postinstall script does not do anything when the
`@tailwindcss/oxide` library is added _unless_ the issue is detected.

[ci-all]

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-05-09 14:55:02 +02:00
Robin Malfait
2f6679abfe
Print sources when DEBUG=* is set (#17952)
This PR improves the debug logging by also adding the provided `@source`
to the log file. It will also print the optimized sources (the ones we
expand and mark as `Auto` or `Pattern`).


In the logs, this will look like this:

```
2025-05-09T11:03:32.906478Z  INFO tailwindcss_oxide::scanner: Provided sources:
2025-05-09T11:03:32.906544Z  INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet", pattern: "**/*", negated: false }
2025-05-09T11:03:32.906589Z  INFO tailwindcss_oxide::scanner: Optimized sources:
2025-05-09T11:03:32.906595Z  INFO tailwindcss_oxide::scanner: Source: Auto { base: "/Users/robin/github.com/RobinMalfait/spreadsheet" }
```

Or if you have more sources:

```
2025-05-09T11:06:54.767546Z  INFO tailwindcss_oxide::scanner: Provided sources:
2025-05-09T11:06:54.767660Z  INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet", pattern: "**/*", negated: false }
2025-05-09T11:06:54.767987Z  INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app", pattern: "./routes/*.{jsx,tsx}", negated: false }
2025-05-09T11:06:54.767992Z  INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app", pattern: "./utils/*.ts", negated: false }
2025-05-09T11:06:54.768450Z  INFO tailwindcss_oxide::scanner: Optimized sources:
2025-05-09T11:06:54.768455Z  INFO tailwindcss_oxide::scanner: Source: Auto { base: "/Users/robin/github.com/RobinMalfait/spreadsheet" }
2025-05-09T11:06:54.768459Z  INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes", pattern: "*.jsx" }
2025-05-09T11:06:54.768462Z  INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes", pattern: "*.tsx" }
2025-05-09T11:06:54.768466Z  INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils", pattern: "*.ts" }
```
2025-05-09 13:39:17 +02:00
Philipp Spiess
ae57d26852
Update contribution docs (#17911)
This PR updates the contribution docs to make it easier for anyone
contributing to Tailwind CSS.
2025-05-09 12:14:32 +02:00
Jordan Pittman
ff9f183368
Fix source map paths in CI (#17938) 2025-05-08 22:56:49 +02:00
Jordan Pittman
56b22bb1d3
Add support for source maps (#17775)
Closes #13694
Closes #13591

# Source Maps Support for Tailwind CSS

This PR adds support for source maps to Tailwind CSS v4 allowing us to
track where styles come from whether that be user CSS, imported
stylesheets, or generated utilities. This will improve debuggability in
browser dev tools and gives us a good foundation for producing better
error messages. I'll go over the details on how end users can enable
source maps, any limitations in our implementation, changes to the
internal `compile(…)` API, and some details and reasoning around the
implementation we chose.

## Usage

### CLI

Source maps can be enabled in the CLI by using the command line argument
`--map` which will generate an inline source map comment at the bottom
of your CSS. A separate file may be generated by passing a file name to
`--map`:

```bash
# Generates an inline source map
npx tailwindcss -i input.css -o output.css --map

# Generates a separate source map file
npx tailwindcss -i input.css -o output.css --map output.css.map
```

### PostCSS

Source maps are supported when using Tailwind as a PostCSS plugin *in
development mode only*. They may or may not be enabled by default
depending on your build tool. If they are not you may be able to
configure them within your PostCSS config:

```jsonc
// package.json
{
  // …
  "postcss": {
    "map": { "inline": true },
    "plugins": {
      "@tailwindcss/postcss": {},
    },
  }
}
```

### Vite

Source maps are supported when using the Tailwind CSS Vite plugin in
*development mode only* by enabling the `css.devSourcemap` setting:

```js
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [tailwindcss()],
  css: {
    devSourcemap: true,
  },
})
```

Now when a CSS file is requested by the browser it'll have an inline
source map comment that the browser can use.

## Limitations

- Production build source maps are currently disabled due to a bug in
Lightning CSS. See
https://github.com/parcel-bundler/lightningcss/pull/971 for more
details.
- In Vite, minified CSS build source maps are not supported at all. See
https://github.com/vitejs/vite/issues/2830 for more details.
- In PostCSS, minified CSS source maps are not supported. This is due to
the complexity required around re-associating every AST node with a
location in the generated, optimized CSS. This complexity would also
have a non-trivial performance impact.

## Testing

Here's how to test the source map functionality in different
environments:

### Testing the CLI

1. Setup typical project that the CLI can use and with sources to scan.

```css
@import "tailwindcss";

@utilty my-custom-utility {
  color: red;
}

/* to test `@apply` */
.card {
  @apply bg-white text-center shadow-md;
}
```

2. Build with source maps:
```bash
bun /path/to/tailwindcss/packages/@tailwindcss-cli/src/index.ts --input input.css -o output.css --map
```

3. Open Chrome DevTools, inspect an element with utility classes, and
you should see rules pointing to `input.css` or
`node_modules/tailwindcss/index.css`

### Testing with Vite

Testing in Vite will require building and installing necessary files
under `dist/*.tgz`.

1. Create a Vite project and enable source maps in `vite.config.js`:
```js
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [tailwindcss()],
  css: {
    // This line is required for them to work
    devSourcemap: true,
  },
})
```

2. Add a component that uses Tailwind classes and custom CSS:
```jsx
// ./src/app.jsx
export default function App() {
  return (
    <div className="bg-blue-500 my-custom-class">
      Hello World
    </div>
  )
}
```

```css
/* ./src/styles.css */
@import "tailwindcss";

@utilty my-custom-utility {
  color: red;
}

/* to test `@apply` */
.card {
  @apply bg-white text-center shadow-md;
}
```

3. Run `npm run dev`, open DevTools, and inspect elements to verify
source mapping works for both utility classes and custom CSS.

### Testing with PostCSS CLI

1. Create a test file and update your PostCSS config:
```css
/* input.css */
@import "tailwindcss";

@layer components {
  .card {
    @apply p-6 rounded-lg shadow-lg;
  }
}
```

```jsonc
// package.json
{
  // …
  "postcss": {
    "map": {
      "inline": true
    },
    "plugins": {
      "/path/to/tailwindcss/packages/packages/@tailwindcss-postcss/src/index.ts": {}
    }
  }
}
```

2. Run PostCSS through Bun:
```bash
bunx --bun postcss ./src/index.css -o out.css
```

3. Inspect the output CSS - it should include an inline source map
comment at the bottom.

### Testing with PostCSS + Next.js

Testing in Next.js will require building and installing necessary files
under `dist/*.tgz`. However, I've not been able to get CSS source maps
to work in Next.js without this hack:

```js
const nextConfig: NextConfig = {
  // next.js overwrites config.devtool so we prevent it from doing so
  // please don't actually do this…
  webpack: (config) =>
    Object.defineProperty(config, "devtool", {
      get: () => "inline-source-map",
      set: () => {},
    }),
};
```

This is definitely not supported and also doesn't work with turbopack.
This can be used to test them temporarily but I suspect that they just
don't work there.

### Manual source map analysis

You can analyze source maps using Evan Wallace's [Source Map
Visualization](https://evanw.github.io/source-map-visualization/) tool
which will help to verify the accuracy and quality of source maps. This
is what I used extensively while developing this implementation.

It'll help verify that custom, user CSS maps back to itself in the
input, that generated utilities all map back to `@tailwind utilities;`,
that source locations from imported files are also handled correctly,
etc… It also highlights the ranges of stuff so it's easy to see if there
are off-by-one errors.

It's easiest to use inline source maps with this tool because you can
take the CSS file and drop it on the page and it'll analyze it while
showing the file content.

If you're using Vite you'll want to access the CSS file with `?direct`
at the end so you don't get a JS module back.

## Implementation

The source map implementation follows the ECMA-426 specification and
includes several key components to aid in that goal:

### Source Location Tracking

Each emittable AST node in the compilation pipeline tracks two types of
source locations:
- `src`: Original source location - [source file, start offset, end
offset]
- `dst`: Generated source location - [output file, start offset, end
offset]

This dual tracking allows us to maintain mappings between the original
source and generated output for things like user CSS, generated
utilities, uses of `@apply`, and tracking theme variables.

It is important to note that source locations for nodes _never overlap_
within a file which helps simplify source map generation. As such each
type of node tracks a specific piece of itself rather than its entire
"block":

| Node | What a `SourceLocation` represents |
| ----------- |
---------------------------------------------------------------- |
| Style Rule | The selector |
| At Rule | Rule name and params, includes the `@` |
| Declaration | Property name and value, excludes the semicolon |
| Comment | The entire comment, includes the start `/*` and end `*/`
markers |

### Windows line endings when parsing CSS

Because our AST tracks nodes through offsets we must ensure that any
mutations to the file do *not* change the lenth of the string. We were
previously replacing `\r\n` with `\n` (see [filter code
points](https://drafts.csswg.org/css-syntax/#css-filter-code-points)
from the spec) — which changes the length of the string and all offsets
may end up incorrect. The CSS parser was updated to handle the CRLF
token directly by skipping over the `\r` and letting remaining code
handle `\n` as it did previously. Some additional tweaks were required
when "peeking" the input but those changes were fairly small.

### Tracking of imports

Source maps need paths to the actual imported stylesheets but the
resolve step for stylesheets happens inside the call to `loadStylesheet`
which make the file path unavailable to us. Because of this the
`loadStylesheet` API was augmented such that it has to return a `path`
property that we can then use to identify imported sources. I've also
made the same change to the `loadModule` API for consistency but nothing
currently uses this property.

The `path` property likely makes `base` redundant but elminating that
(if we even want to) is a future task.

### Optimizing the AST

Our optimization pass may intoduce some nodes, for example, fallbacks we
create for `@property`. These nodes are linked back to `@tailwind
utilities` as ultimately that is what is responsible for creating them.

### Line Offset Tables

A key component to our source map generation is the line offset table,
which was inspired by some ESBuild internals. It stores a sorted list of
offsets for the start of each line allowing us to translate offsets to
line/column `Position`s in `O(log N)` time and from `Position`s to
offsets in `O(1)` time. Creation of the table takes `O(N)` time.

This means that we can store code point offsets for source locations and
not have to worry about computing or tracking line/column numbers during
parsing and serialization. Only when a source map is generated do these
offsets need to be computed. This ensures the performance penalty when
not using source maps is minimal.

### Source Map Generation

The source map returned by `buildSourceMap()` is designed to follow the
[ECMA-426 spec](https://tc39.es/ecma426). Because that spec is not
completely finalized we consider the result of `buildSourceMap()` to be
internal API that may change as the spec chamges.

The produces source map is a "decoded" map such that all sources and
mappings are in an object graph. A library like `source-map-js` must be
used to convert this to an encoded source map of the right version where
mappings are encoded with base 64 VLQs.

Any specific integration (Vite, PostCSS, etc…) can then use
`toSourceMap()` from `@tailwindcss/node` to convert from the internal
source map to an spec-compliant encoded source map that can be
understood by other tools.

### Handling minification in Lightning

Since we use Lightning CSS for optimization, and it takes in an input
map, we generate an encoded source map that we then pass to lightning.
The output source map *from lighting itself* is then passed back in
during the second optimization pass. The final map is then passed from
lightning to the CLI (but not Vite or PostCSS — see the limitations
section for details).

In some cases we have to "fix up" the output CSS. When this happens we
use `magic-string` to do the replacement in a way that is trackable and
`@amppproject/remapping` to map that change back onto the original
source map. Once the need for these fix ups disappear these dependencies
can go away.

Notes:
- The accuracy of source maps run though lightning is reduced as it only
tracks on a per-rule level. This is sufficient enough for browser dev
tools so should be fine.
- Source maps during optimization do not function properly at this time
because of a bug in Lightning CSS regarding license comments. Once this
bug is fixed they will start working as expected.

### How source locations flow through the system

1. During initial CSS parsing, source locations are preserved.
2. During parsing these source locations are also mapped to the
destinations which supports an optimization for when no utilities are
generated.
3. Throughout the compilation process, transformations maintain source
location data
4. Generated utilities are explicitly pointed to `@tailwind utilities`
unless generated by `@apply`.
5. When optimization is enabled, source maps are remapped through
lightningcss
6. Final source maps are written in the requested format (inline or
separate file)
2025-05-08 16:29:49 -04:00
Robin Malfait
62706dccb0
Trigger updates to the internal upgrades repo (#17928)
This PR updates will now trigger the new internal `upgrades` repo
instead of the Tailwind Play repo directly.

We will be updating more internal repos when a new version is published.
We will also use that new repo to update our other repos for other
published packages in the future.
2025-05-08 18:46:27 +02:00
Philipp Spiess
17ca56d386
Fix bug with nested @apply rules in utility classes (#17924) (#17925)
Fixes #17924

When an `@apply` pointed to utility that nested usages of `@apply`,
these nested usages were not properly carried through the dependency
chain. This was because we were only tracking dependencies on the
immediate parent rather than all parents.

To fix this, this PR:

- Modifies the dependency resolution to track dependencies through the
entire parent path
- Uses a `walk(…)` for the node replacement logic so that all nested
`@apply` usages are also resolved (as these are now tracked in the
dependency list anyways

## Test Plan

- Added a regression test for #17924 to the unit tests and ensure
existing tests don't break
2025-05-08 18:26:07 +02:00
Philipp Spiess
179e5ddd7c
Add more folders to the list of ignored content dirs (#17892)
Closes #15452

This PR adds more directories to the list of ignored content dirs. These
are now handled the same as `node_modules`:

- Contents of this directory are ignored by default to avoid scanning
dependency trees
- Explicit `@import`s inside these folders are now treated as
_external_, bypassing any `.gitignore` files.

The new extensions are:

- Version control systems: `.hg`, `.svn`
- Bundler caches: `.venv`, `venv`, `.yarn`
- Framework caches: `.next`, `.turbo`, `.parcel-cache`, `__pycache__`,
`.svelte-kit`

## Test plan

Verified with the repro of #15452 by renaming the ignored directory to
`.venv` and installing a local tarball:

<img width="1283" alt="Screenshot 2025-05-06 at 13 14 55"
src="https://github.com/user-attachments/assets/3265acbb-e121-47b3-ac6c-e174770f8234"
/>
2025-05-08 11:27:11 +02:00
Robin Malfait
d8c4df8001
Write to log file when using DEBUG=* (#17906)
This PR improves the debugability of the scanner when using `DEBUG=*` by
writing to a `tailwindcss-{pid}.log` file in the current working
directory.

It will include all the tracing information from the scanner. This PR
also introduces `Discovering {path}` and `Reading {path}` logs.

- `Discovering {path}` — this is logged when we are traversing the file
system looking for files. We use the `ignore` crate, and log this
information in the `filter_entry` callback. If a file was already
ignored by `.gitignore` files, this won't show up, but it also means
that we will not read it.
- `Reading {path}` — this is when we are actually reading the file so we
can start extracting potential Tailwind CSS classes.

These will give you some insights in what paths are being scanned, and
if we get stuck, where we get stuck.

Also, we are appending to the file. In the log below, you can already
see that a `tailwindcss-<number>.log` file exists already even though it
didn't exist before running the command. This should make it easier to
debug if we get stuck on a specific file/folder because the file will be
populated with information.

There are a few reasons for appending to a file:
1. There is a lot of output, so spamming the stdout/stderr is not ideal
2. If you run the same command again, after changing your `@source`
directives, you could diff the outputs. (although, the timestamps will
be different)
3. When using `DEBUG=*`, a lot of other tools also output debug
information, so writing to a file should make this better.

<details>

<summary>Example log</summary>

```log
2025-05-06T23:13:45.912292Z  INFO scan_sources: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.912697Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/out.css"
2025-05-06T23:13:45.912716Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/tailwindcss-61347.log"
2025-05-06T23:13:45.912748Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app"
2025-05-06T23:13:45.912786Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/test"
2025-05-06T23:13:45.912814Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/test/utils.ts"
2025-05-06T23:13:45.912851Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils"
2025-05-06T23:13:45.912873Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/flatten.ts"
2025-05-06T23:13:45.912884Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/matrix.ts"
2025-05-06T23:13:45.912893Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/default-map.ts"
2025-05-06T23:13:45.912904Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/tailwind.css"
2025-05-06T23:13:45.912914Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/root.tsx"
2025-05-06T23:13:45.912936Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain"
2025-05-06T23:13:45.912962Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.ts"
2025-05-06T23:13:45.912972Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/walk-ast.ts"
2025-05-06T23:13:45.912995Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature"
2025-05-06T23:13:45.913019Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.test.ts"
2025-05-06T23:13:45.913029Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.test.ts"
2025-05-06T23:13:45.913039Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.ts"
2025-05-06T23:13:45.913048Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.ts"
2025-05-06T23:13:45.913058Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.ts"
2025-05-06T23:13:45.913067Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.test.ts"
2025-05-06T23:13:45.913077Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/ast.ts"
2025-05-06T23:13:45.913086Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.ts"
2025-05-06T23:13:45.913095Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.ts"
2025-05-06T23:13:45.913105Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.test.ts"
2025-05-06T23:13:45.913121Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation-result.ts"
2025-05-06T23:13:45.913505Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tmp.test.ts"
2025-05-06T23:13:45.913514Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.test.ts"
2025-05-06T23:13:45.913523Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.test.ts"
2025-05-06T23:13:45.913531Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.ts"
2025-05-06T23:13:45.913554Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions"
2025-05-06T23:13:45.913583Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.test.ts"
2025-05-06T23:13:45.913592Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.ts"
2025-05-06T23:13:45.913601Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.test.ts"
2025-05-06T23:13:45.913613Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.ts"
2025-05-06T23:13:45.913622Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.test.ts"
2025-05-06T23:13:45.913631Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.ts"
2025-05-06T23:13:45.913640Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.ts"
2025-05-06T23:13:45.913648Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.test.ts"
2025-05-06T23:13:45.913656Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.ts"
2025-05-06T23:13:45.913665Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.test.ts"
2025-05-06T23:13:45.913673Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.ts"
2025-05-06T23:13:45.913681Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.ts"
2025-05-06T23:13:45.913689Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.test.ts"
2025-05-06T23:13:45.913697Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.ts"
2025-05-06T23:13:45.913705Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.test.ts"
2025-05-06T23:13:45.913713Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.ts"
2025-05-06T23:13:45.913720Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.ts"
2025-05-06T23:13:45.913728Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.test.ts"
2025-05-06T23:13:45.913741Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/index.ts"
2025-05-06T23:13:45.913749Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.test.ts"
2025-05-06T23:13:45.913757Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.test.ts"
2025-05-06T23:13:45.913783Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/__snapshots__"
2025-05-06T23:13:45.913817Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.test.ts"
2025-05-06T23:13:45.913826Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.ts"
2025-05-06T23:13:45.913833Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/function-utils.ts"
2025-05-06T23:13:45.913841Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.ts"
2025-05-06T23:13:45.913849Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.test.ts"
2025-05-06T23:13:45.913857Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.test.ts"
2025-05-06T23:13:45.913879Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes"
2025-05-06T23:13:45.913896Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes/_index.tsx"
2025-05-06T23:13:45.914172Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/README.md"
2025-05-06T23:13:45.914197Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/public"
2025-05-06T23:13:45.914228Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/public/fonts"
2025-05-06T23:13:45.914268Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/package.json"
2025-05-06T23:13:45.914289Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/scripts"
2025-05-06T23:13:45.914310Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/scripts/generate-documentation.ts"
2025-05-06T23:13:45.914332Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/.github"
2025-05-06T23:13:45.914383Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/.github/workflows"
2025-05-06T23:13:45.914410Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/.github/workflows/ci.yml"
2025-05-06T23:13:45.914420Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/tsconfig.json"
2025-05-06T23:13:45.914455Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/vite.config.ts"
2025-05-06T23:13:45.914486Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/biome.json"
2025-05-06T23:13:45.914512Z  INFO scan_sources: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.914515Z  INFO extract_candidates: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.914518Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.914524Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: Reading 58 file(s)
2025-05-06T23:13:45.914808Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/tailwindcss-61347.log"
2025-05-06T23:13:45.914990Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes/_index.tsx"
2025-05-06T23:13:45.915138Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/.github/workflows/ci.yml"
2025-05-06T23:13:45.915140Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.test.ts"
2025-05-06T23:13:45.915145Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.ts"
2025-05-06T23:13:45.915163Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/tsconfig.json"
2025-05-06T23:13:45.915153Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.ts"
2025-05-06T23:13:45.915226Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/matrix.ts"
2025-05-06T23:13:45.915229Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/walk-ast.ts"
2025-05-06T23:13:45.915372Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/test/utils.ts"
2025-05-06T23:13:45.915578Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/ast.ts"
2025-05-06T23:13:45.915599Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.ts"
2025-05-06T23:13:45.915637Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.ts"
2025-05-06T23:13:45.915647Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.ts"
2025-05-06T23:13:45.915657Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/vite.config.ts"
2025-05-06T23:13:45.915680Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.test.ts"
2025-05-06T23:13:45.915681Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/flatten.ts"
2025-05-06T23:13:45.915701Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.test.ts"
2025-05-06T23:13:45.915706Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/README.md"
2025-05-06T23:13:45.915691Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/default-map.ts"
2025-05-06T23:13:45.915701Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.test.ts"
2025-05-06T23:13:45.915730Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/package.json"
2025-05-06T23:13:45.915753Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.ts"
2025-05-06T23:13:45.915731Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/biome.json"
2025-05-06T23:13:45.915787Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.test.ts"
2025-05-06T23:13:45.915822Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.test.ts"
2025-05-06T23:13:45.915826Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.ts"
2025-05-06T23:13:45.915885Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/root.tsx"
2025-05-06T23:13:45.915885Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.ts"
2025-05-06T23:13:45.915886Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.ts"
2025-05-06T23:13:45.915990Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.test.ts"
2025-05-06T23:13:45.915995Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.test.ts"
2025-05-06T23:13:45.915998Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.ts"
2025-05-06T23:13:45.916000Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.ts"
2025-05-06T23:13:45.916024Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.ts"
2025-05-06T23:13:45.916056Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/function-utils.ts"
2025-05-06T23:13:45.916058Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/scripts/generate-documentation.ts"
2025-05-06T23:13:45.916070Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.ts"
2025-05-06T23:13:45.916075Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.test.ts"
2025-05-06T23:13:45.916063Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.test.ts"
2025-05-06T23:13:45.916119Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation-result.ts"
2025-05-06T23:13:45.916120Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/index.ts"
2025-05-06T23:13:45.916148Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.test.ts"
2025-05-06T23:13:45.916152Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.test.ts"
2025-05-06T23:13:45.916193Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.ts"
2025-05-06T23:13:45.916219Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tmp.test.ts"
2025-05-06T23:13:45.916228Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.test.ts"
2025-05-06T23:13:45.916245Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.test.ts"
2025-05-06T23:13:45.916256Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.ts"
2025-05-06T23:13:45.916253Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.test.ts"
2025-05-06T23:13:45.916267Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.test.ts"
2025-05-06T23:13:45.916287Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.test.ts"
2025-05-06T23:13:45.916286Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.test.ts"
2025-05-06T23:13:45.916317Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.test.ts"
2025-05-06T23:13:45.916323Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.ts"
2025-05-06T23:13:45.916354Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.ts"
2025-05-06T23:13:45.916562Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.ts"
2025-05-06T23:13:45.916609Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.ts"
2025-05-06T23:13:45.916676Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.916682Z  INFO extract_candidates:parse_all_blobs: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.916688Z  INFO extract_candidates:parse_all_blobs:extract: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918271Z  INFO extract_candidates:parse_all_blobs:extract: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918282Z  INFO extract_candidates:parse_all_blobs: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918286Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918288Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: Reading 2 file(s)
2025-05-06T23:13:45.918315Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/tailwind.css"
2025-05-06T23:13:45.918504Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/out.css"
2025-05-06T23:13:45.918512Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918519Z  INFO extract_candidates:extract_css_variables: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918522Z  INFO extract_candidates:extract_css_variables:extract: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918635Z  INFO extract_candidates:extract_css_variables:extract: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918640Z  INFO extract_candidates:extract_css_variables: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.919059Z  INFO extract_candidates: tailwindcss_oxide::scanner: exit
```

</details>

We also output where we are writing the file to. This looks like this
when using the CLI:
<img width="1462" alt="image"
src="https://github.com/user-attachments/assets/79c2cc95-adea-4bbd-a4f1-101de45726f5"
/>

Last but not least, this also ignores `.log` files by default

## Test plan

Ran the CLI (but you can use any tool real, since this is implemented in
Oxide) with the `DEBUG=*` flag.
The file generated, looks like the example I shared above.
2025-05-07 10:19:54 -04:00
Robin Malfait
449dfcf00d
Make upgrades faster (#17898)
This PR fixes an issue where the upgrade tests were taking too long.
This PR fixes that.

Essentially when updating dependencies we did this:
```sh
npm install tailwindcss@latest
npm install @tailwindcss/postcss@latest
npm install prettier-plugin-tailwindcss@latest
```

But this is not ideal, because we are calling out to `npm` and run each
dependency in isolation.

With this PR, we essentially do it all in one go:
```sh
npm install tailwindcss@latest @tailwindcss/postcss@latest prettier-plugin-tailwindcss@latest
```

## Test plan

Testing this locally, the results look like this:

| Before | After |
|--------|-------|
| <img width="590" alt="image"
src="https://github.com/user-attachments/assets/c899d432-78c3-4945-af73-3ef4fffa08da"
/> | <img width="656" alt="image"
src="https://github.com/user-attachments/assets/a448d711-dd74-44cf-9790-c8a14fc6964f"
/> |


In CI:

| Before (with a failure) | After |
| --- | --- |
| <img width="224" alt="image"
src="https://github.com/user-attachments/assets/f58a6bf6-fdbe-4474-aa1f-444ab51a53c9"
/> | <img width="224" alt="image"
src="https://github.com/user-attachments/assets/54606df5-4f69-444b-8d4c-5ce27c5d6b41"
/> |

[ci-all]
2025-05-06 20:02:13 +02:00
Brandon McConnell
4f8539c063
Fix bug replacing modifier variable shorthand syntax underscores (#17889)
Resolves #17888

**Reproduction URL:** https://play.tailwindcss.com/YvIekuzVRd

Changes:
* Don't use `decodeArbitraryValue` when parsing variable shorthand
syntax in modifiers
* replace `decodeArbitraryValue(modifier.slice(1, -1))` with
`modifier.slice(1, -1)`
  * added test case, passing ✅

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-05-06 09:32:47 -04:00
depfu[bot]
ed45952d15
Update turbo 2.5.0 → 2.5.2 (patch) (#17887) 2025-05-06 12:54:26 +02:00
Robin Malfait
5c5ae04db6
Fix Windows CI build (#17878)
This PR fixes a Windows CI issue due to the recently merged #17846

There might be better ways to solve this, but essentially we want to
make sure we are always dealing with `\n` and not `\r\n`. This PR fixes
that by replacing all `\r\n` with `\n` in the tests.
2025-05-05 17:38:53 +02:00
Jordan Pittman
6a1df6acf6
Ignore @tailwind utilities inside @reference (#17836)
You can use `@reference "tailwindcss"` or `@reference
"../path/to/your/css/file.css"` to reference your theme for use in
`@apply`, `theme(…)`, etc…

Unfortunatley, because the imported file still contains `@tailwind
utilities` it would trigger a re-scan of the filesystem — even though
the use of `@reference` ensures that no CSS can actually be output by
the import itself.

This PR does two things:
- Adds some explicit feature detection tests for what features we pick
up in a stylesheet based on the CSS written and how things are imported
- Explicitly ignores `@tailwind utilities` inside of `@reference` so it
isn't a trigger for file scanning

Because of how Vite itself handles dependencies editing files on disk
will still trigger a rebuild of any file using `@reference`. This is
because Vite rebuilds files when _any_ of its transitive dependencies
change.

For example, given this Vue file:
```vue
<style>
@reference "./styles.css";
</style>
<template> <!-- ... --> </template>
```

And this stylesheet:
```css
@import "tailwindcss";
```

The dependency chain looks like this: `file.vue -> styles.css -> {all
the sources in your project}`

Vite sees that a file (e.g. `index.html`) has changed, thus `styles.css`
needs change, which means `file.vue` needs to be compiled again as well.
Now in reality we depend on the _on disk_ version of styles.css not the
compiled version but Vite itself doesn't know that (or have a way to
indicate this afaik).

Coming up with a solution to that problem will have to be a separate PR
— but there is a workaround:

### 1. Inline the imports from `@import "tailwindcss";`

Replace this in your main stylesheet:
```css
@import "tailwindcss";
```

with this (this is basically what `node_modules/tailwindcss/index.css`
is):
```css
@layer theme, base, components, utilities;

@import 'tailwindcss/theme' layer(theme);
@import 'tailwindcss/preflight' layer(base);
@import 'tailwindcss/utilities' layer(utilities);

/* the rest of your styles imports, styles, etc… */
```

### 2. Split your stylesheet into "main" and "theme" parts

Your "theme" is comprised of the `@import 'tailwindcss/theme'
layer(theme);` import, any custom `@theme` blocks, any `@config`
directives, and any `@plugin` directives. Move all of these into their
own file.

For example, replace this with two files:

```css
@layer theme, base, components, utilities;
@import 'tailwindcss/theme' layer(theme);
@import 'tailwindcss/preflight' layer(base);
@import 'tailwindcss/utilities' layer(utilities);

@theme {
  --color-primary: #c0ffee;
}

@plugin "./my-plugin.js";

/* the rest of your styles imports, styles, etc… */
```

with a theme file:
```css
@import 'tailwindcss/theme' layer(theme);

/* all your `@theme` stuff goes in this file */
@theme {
  --color-primary: #c0ffee;
}

/* additionally any @config or @plugin does too */
@plugin "./my-plugin.js";
```

and your main CSS file:
```css
@layer theme, base, components, utilities;
@import './my-theme.css'; /* I replaced this import */
@import 'tailwindcss/preflight' layer(base);
@import 'tailwindcss/utilities' layer(utilities);

/* the rest of your styles imports, styles, etc… */
```

### 3. Import only your "theme" file in your Vue components / CSS
modules / etc…

```vue
<style>
@reference "./my-theme.css";
</style>
<template> <!-- ... --> </template>
```

Fixes #17693
2025-05-05 11:21:55 -04:00
Robin Malfait
d38554d110
Fix HAML extraction with embedded Ruby (#17846)
This PR fixes and improves the HAML extractor by ensuring that whenever
we detect lines or blocks of Ruby code (`-`, `=` and `~`) characters at
the beginning of the line, we treat them as Ruby code.

Fixes: #17813

## Test Plan

1. Existing tests pass
2. Changed 1 existing test which embedded Ruby syntax
3. Added a dedicated test to ensure the HAML file in the linked issue is
parsed correctly

Running this in the internal extractor tool you can see that the
`w-[12px]`, `w-[16px]`, `h-[12px]`, and `h-[16px]` are properly
extracted.

Note: the `mr-12px` is also extracted, but not highlighted because this
is not a valid Tailwind CSS class.

<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/fc5929ca-bc71-47d2-b21b-7abeec86f54d"
/>
2025-05-05 10:26:17 -04:00
depfu[bot]
473f0241bf
Update h3 1.15.1 → 1.15.3 (patch) (#17873) 2025-05-05 10:30:10 +00:00
depfu[bot]
e00d0926eb
Update @vitejs/plugin-react 4.3.4 → 4.4.1 (minor) (#17862) 2025-05-04 19:28:46 +02:00
depfu[bot]
dd5ec49606
Update eslint 9.24.0 → 9.25.1 (minor) (#17850)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.24.0 → 9.25.1) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.25.1">9.25.1</a></h4>

<blockquote><h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="cdc8e8c950"><code
class="notranslate">cdc8e8c</code></a> fix: revert directive detection
in no-unused-expressions (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19639">#19639</a>)
(sethamus)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="1f2b057ddc"><code
class="notranslate">1f2b057</code></a> chore: upgrade @eslint/js@9.25.1
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19642">#19642</a>)
(Milos Djermanovic)</li>
<li>
<a
href="771317fa93"><code
class="notranslate">771317f</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
</ul></blockquote>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.25.0">9.25.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="dcd95aafa3"><code
class="notranslate">dcd95aa</code></a> feat: support TypeScript syntax
in no-empty-function rule (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19551">#19551</a>)
(sethamus)</li>
<li>
<a
href="77d6d5bc49"><code
class="notranslate">77d6d5b</code></a> feat: support TS syntax in <code
class="notranslate">no-unused-expressions</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19564">#19564</a>)
(Sweta Tanwar)</li>
<li>
<a
href="90228e5d57"><code
class="notranslate">90228e5</code></a> feat: support <code
class="notranslate">JSRuleDefinition</code> type (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19604">#19604</a>)
(루밀LuMir)</li>
<li>
<a
href="59ba6b7378"><code
class="notranslate">59ba6b7</code></a> feat: add allowObjects option to
no-restricted-properties (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19607">#19607</a>)
(sethamus)</li>
<li>
<a
href="db650a036b"><code
class="notranslate">db650a0</code></a> feat: support TypeScript syntax
in <code class="notranslate">no-invalid-this</code> rule (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19532">#19532</a>)
(Tanuj Kanti)</li>
<li>
<a
href="9535cffe7b"><code
class="notranslate">9535cff</code></a> feat: support TS syntax in <code
class="notranslate">no-loop-func</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19559">#19559</a>)
(Nitin Kumar)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="910bd13c4c"><code
class="notranslate">910bd13</code></a> fix: <code
class="notranslate">nodeTypeKey</code> not being used in <code
class="notranslate">NodeEventGenerator</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19631">#19631</a>)
(StyleShit)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="ca7a735dde"><code
class="notranslate">ca7a735</code></a> docs: update <code
class="notranslate">no-undef-init</code> when not to use section (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19624">#19624</a>)
(Tanuj Kanti)</li>
<li>
<a
href="1b870c9da4"><code
class="notranslate">1b870c9</code></a> docs: use <code
class="notranslate">eslint-config-xo</code> in the getting started guide
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19629">#19629</a>)
(Nitin Kumar)</li>
<li>
<a
href="5d4af16ab1"><code
class="notranslate">5d4af16</code></a> docs: add types for multiple rule
options (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19616">#19616</a>)
(Tanuj Kanti)</li>
<li>
<a
href="e8f8d57bd6"><code
class="notranslate">e8f8d57</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="a40348f1f6"><code
class="notranslate">a40348f</code></a> docs: no-use-before-define tweaks
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19622">#19622</a>)
(Kirk Waiblinger)</li>
<li>
<a
href="0ba3ae3e5a"><code
class="notranslate">0ba3ae3</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="865dbfed6c"><code
class="notranslate">865dbfe</code></a> docs: ensure "learn more"
deprecation links point to useful resource (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19590">#19590</a>)
(Kirk Waiblinger)</li>
<li>
<a
href="f80b746d85"><code
class="notranslate">f80b746</code></a> docs: add known limitations for
no-self-compare (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19612">#19612</a>)
(Nitin Kumar)</li>
<li>
<a
href="865aed6293"><code
class="notranslate">865aed6</code></a> docs: Update README (GitHub
Actions Bot)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="88dc1965a4"><code
class="notranslate">88dc196</code></a> chore: upgrade @eslint/js@9.25.0
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19636">#19636</a>)
(Milos Djermanovic)</li>
<li>
<a
href="345288d7b2"><code
class="notranslate">345288d</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="affe6be018"><code
class="notranslate">affe6be</code></a> chore: upgrade trunk (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19628">#19628</a>)
(sethamus)</li>
<li>
<a
href="dd20cf274e"><code
class="notranslate">dd20cf2</code></a> test: fix <code
class="notranslate">no-loop-func</code> test with duplicate variable
reports (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19610">#19610</a>)
(Milos Djermanovic)</li>
<li>
<a
href="bd05397ef6"><code
class="notranslate">bd05397</code></a> chore: upgrade <code
class="notranslate">@eslint/*</code> dependencies (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19606">#19606</a>)
(Milos Djermanovic)</li>
<li>
<a
href="22ea18b8ba"><code
class="notranslate">22ea18b</code></a> chore: replace invalid <code
class="notranslate">int</code> type with <code
class="notranslate">number</code> inside JSDocs. (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19597">#19597</a>)
(Arya Emami)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="d49f5b7333...3ed4b3652d">See
the full diff on Github</a>. The new version differs by 29 commits:</p>
<ul>
<li><a
href="3ed4b3652d"><code>9.25.1</code></a></li>
<li><a
href="7a19ccd052"><code>Build:
changelog update for 9.25.1</code></a></li>
<li><a
href="1f2b057ddc"><code>chore:
upgrade @eslint/js@9.25.1 (#19642)</code></a></li>
<li><a
href="771317fa93"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="cdc8e8c950"><code>fix:
revert directive detection in no-unused-expressions
(#19639)</code></a></li>
<li><a
href="e62e267615"><code>9.25.0</code></a></li>
<li><a
href="bc2c3e6acc"><code>Build:
changelog update for 9.25.0</code></a></li>
<li><a
href="88dc1965a4"><code>chore:
upgrade @eslint/js@9.25.0 (#19636)</code></a></li>
<li><a
href="345288d7b2"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="910bd13c4c"><code>fix:
`nodeTypeKey` not being used in `NodeEventGenerator`
(#19631)</code></a></li>
<li><a
href="ca7a735dde"><code>docs:
update `no-undef-init` when not to use section (#19624)</code></a></li>
<li><a
href="affe6be018"><code>chore:
upgrade trunk (#19628)</code></a></li>
<li><a
href="1b870c9da4"><code>docs:
use `eslint-config-xo` in the getting started guide
(#19629)</code></a></li>
<li><a
href="5d4af16ab1"><code>docs:
add types for multiple rule options (#19616)</code></a></li>
<li><a
href="e8f8d57bd6"><code>docs:
Update README</code></a></li>
<li><a
href="a40348f1f6"><code>docs:
no-use-before-define tweaks (#19622)</code></a></li>
<li><a
href="0ba3ae3e5a"><code>docs:
Update README</code></a></li>
<li><a
href="865dbfed6c"><code>docs:
ensure &quot;learn more&quot; deprecation links point to useful resource
(#19590)</code></a></li>
<li><a
href="dcd95aafa3"><code>feat:
support TypeScript syntax in no-empty-function rule
(#19551)</code></a></li>
<li><a
href="77d6d5bc49"><code>feat:
support TS syntax in `no-unused-expressions` (#19564)</code></a></li>
<li><a
href="90228e5d57"><code>feat:
support `JSRuleDefinition` type (#19604)</code></a></li>
<li><a
href="f80b746d85"><code>docs:
add known limitations for no-self-compare (#19612)</code></a></li>
<li><a
href="59ba6b7378"><code>feat:
add allowObjects option to no-restricted-properties
(#19607)</code></a></li>
<li><a
href="db650a036b"><code>feat:
support TypeScript syntax in `no-invalid-this` rule
(#19532)</code></a></li>
<li><a
href="dd20cf274e"><code>test:
fix `no-loop-func` test with duplicate variable reports
(#19610)</code></a></li>
<li><a
href="9535cffe7b"><code>feat:
support TS syntax in `no-loop-func` (#19559)</code></a></li>
<li><a
href="bd05397ef6"><code>chore:
upgrade `@eslint/*` dependencies (#19606)</code></a></li>
<li><a
href="22ea18b8ba"><code>chore:
replace invalid `int` type with `number` inside JSDocs.
(#19597)</code></a></li>
<li><a
href="865aed6293"><code>docs:
Update README</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-05-03 01:04:36 +02:00
Robin Malfait
c095071f22
Skip .css files when migrating templates (#17854)
This PR fixes an issue where the upgrade tool also migrates `.css` files
as-if they are content files. This is not the intended behavior.

## Test plan

Ran this on my personal website. 

Before:
<img width="1316" alt="image"
src="https://github.com/user-attachments/assets/2b7337c6-7b88-4811-911f-139ab2e31b3b"
/>

After: 
<img width="1046" alt="image"
src="https://github.com/user-attachments/assets/55f09355-37cb-419b-9924-973cf2681c1d"
/>
2025-05-03 00:48:45 +02:00
Robin Malfait
4e4275638f
Design system driven upgrade migrations (#17831)
This PR introduces a vastly improved upgrade migrations system, to
migrate your codebase and modernize your utilities to make use of the
latest variants and utilities.

It all started when I saw this PR the other day:
https://github.com/tailwindlabs/tailwindcss/pull/17790

I was about to comment "Don't forget to add a migration". But I've been
thinking about a system where we can automate this process away. This PR
introduces this system.

This PR introduces upgrade migrations based on the internal Design
System, and it mainly updates arbitrary variants, arbitrary properties
and arbitrary values.

## The problem

Whenever we ship new utilities, or you make changes to your CSS file by
introducing new `@theme` values, or adding new `@utility` rules. It
could be that the rest of your codebase isn't aware of that, but you
could be using these values.

For example, it could be that you have a lot of arbitrary properties in
your codebase, they look something like this:

```html
<div class="[color-scheme:dark] [text-wrap:balance]"></div>
```

Whenever we introduce new features in Tailwind CSS, you probably don't
keep an eye on the release notes and update all of these arbitrary
properties to the newly introduced utilities.

But with this PR, we can run the upgrade tool:

```console
npx -y @tailwindcss/upgrade@latest
```

...and it will upgrade your project to use the new utilities:

```html
<div class="scheme-dark text-balance"></div>
```

It also works for arbitrary values, for example imagine you have classes
like this:

```html
<!-- Arbitrary property -->
<div class="[max-height:1lh]"></div>

<!-- Arbitrary value -->
<div class="max-h-[1lh]"></div>
```

Running the upgrade tool again:

```console
npx -y @tailwindcss/upgrade@latest
```

... gives you the following output:

```html
<!-- Arbitrary property -->
<div class="max-h-lh"></div>

<!-- Arbitrary value -->
<div class="max-h-lh"></div>
```

This is because of the original PR I mentioned, which introduced the
`max-h-lh` utilities.

A nice benefit is that this output only has 1 unique class instead of 2,
which also potentially reduces the size of your CSS file.

It could also be that you are using arbitrary values where you (or a
team member) didn't even know an alternative solution existed.

E.g.:

```html
<div class="w-[48rem]"></div>
```

After running the upgrade tool you will get this:

```html
<div class="w-3xl"></div>
```

We can go further though. Since the release of Tailwind CSS v4, we
introduced the concept of "bare values". Essentially allowing you to
type a number on utilities where it makes sense, and we produce a value
based on that number.

So an input like this:

```html
<div class="border-[123px]"></div>
```

Will be optimized to just:

```html
<div class="border-123"></div>
```

This can be very useful for complex utilities, for example, how many
times have you written something like this:

```html
<div class="grid-cols-[repeat(16,minmax(0,1fr))]"></div>
```

Because up until Tailwind CSS v4, we only generated 12 columns by
default. But since v4, we can generate any number of columns
automatically.

Running the migration tool will give you this:

```html
<div class="grid-cols-16"></div>
```

### User CSS

But, what if I told you that we can keep going...

In [Catalyst](https://tailwindcss.com/plus/ui-kit) we often use classes
that look like this for accessibility reasons:

```html
<div class="text-[CanvasText] bg-[Highlight]"></div>
```

What if you want to move the `CanvasText` and `Highlight` colors to your
CSS:

```css
@import "tailwincdss";

@theme {
  --color-canvas: CanvasText;
  --color-highlight: Highlight;
}
```

If you now run the upgrade tool again, this will be the result:

```html
<div class="text-canvas bg-highlight"></div>
```

We never shipped a `text-canvas` or `bg-highlight` utility, but the
upgrade tool uses your own CSS configuration to migrate your codebase.

This will keep your codebase clean, consistent and modern and you are in
control.

Let's look at one more example, what if you have this in a lot of
places:

```html
<div class="[scrollbar-gutter:stable]"></div>
```

And you don't want to wait for the Tailwind CSS team to ship a
`scrollbar-stable` (or similar) feature. You can add your own utility:

```css
@import "tailwincdss";

@utility scrollbar-stable {
  scrollbar-gutter: stable;
}
```

```html
<div class="scrollbar-stable"></div>
```

## The solution — how it works

There are 2 big things happening here:

1. Instead of us (the Tailwind CSS team) hardcoding certain migrations,
we will make use of the internal `DesignSystem` which is the source of
truth for all this information. This is also what Tailwind CSS itself
uses to generate the CSS file.

   The internal `DesignSystem` is essentially a list of all:

   1. The internal utilities
   2. The internal variants
   3. The default theme we ship
   4. The user CSS
      1. With custom `@theme` values
      2. With custom `@custom-variant` implementations
      3. With custom `@utility` implementations
2. The upgrade tool now has a concept of `signatures`

The signatures part is the most interesting one, and it allows us to be
100% sure that we can migrate your codebase without breaking anything.

A signature is some unique identifier that represents a utility. But 2
utilities that do the exact same thing will have the same signature.

To make this work, we have to make sure that we normalize values. One
such value is the selector. I think a little visualization will help
here:

| UTILITY          | GENERATED SIGNATURE     |
| ---------------- | ----------------------- |
| `[display:flex]` | `.x { display: flex; }` |
| `flex`           | `.x { display: flex; }` |

They have the exact same signature and therefore the upgrade tool can
safely migrate them to the same utility.

For this we will prefer the following order:

1. Static utilities — essentially no brackets. E.g.: `flex`,
`grid-cols-2`
2. Arbitrary values — e.g.: `max-h-[1lh]`, `border-[2px]`
3. Arbitrary properties — e.g.: `[color-scheme:dark]`, `[display:flex]`

We also have to canonicalize utilities to there minimal form.
Essentially making sure we increase the chance of finding a match.

```
[display:_flex_] → [display:flex] → flex
[display:_flex]  → [display:flex] → flex
[display:flex_]  → [display:flex] → flex
[display:flex]   → [display:flex] → flex
```

If we don't do this, then the signatures will be slightly different, due
to the whitespace:

| UTILITY            | GENERATED SIGNATURE       |
| ------------------ | ------------------------- |
| `[display:_flex_]` | `.x { display:  flex ; }` |
| `[display:_flex]`  | `.x { display:  flex; }`  |
| `[display:flex_]`  | `.x { display: flex ; }`  |
| `[display:flex]`   | `.x { display: flex; }`   |

### Other small improvements

A few other improvements are for optimizing existing utilities:

1. Remove unnecessary data types. E.g.:

   - `bg-[color:red]` -> `bg-[red]`
- `shadow-[shadow:inset_0_1px_--theme(--color-white/15%)]` ->
`shadow-[inset_0_1px_--theme(--color-white/15%)]`

This also makes use of these signatures and if dropping the data type
results in the same signature then we can safely drop it.

Additionally, if a more specific utility exists, we will prefer that
one. This reduced ambiguity and the need for data types.

   - `bg-[position:123px]` → `bg-position-[123px]`
   - `bg-[123px]` → `bg-position-[123px]`
   - `bg-[size:123px]` → `bg-size-[123px]`


2. Optimizing modifiers. E.g.:
   - `bg-red-500/[25%]` → `bg-red-500/25`
   - `bg-red-500/[100%]` → `bg-red-500`
   - `bg-red-500/100` → `bg-red-500`

3. Hoist `not` in arbitrary variants

- `[@media_not_(prefers-color-scheme:dark)]:flex` →
`not-[@media_(prefers-color-scheme:dark)]:flex` → `not-dark:flex` (in
case you are using the default `dark` mode implementation

4. Optimize raw values that could be converted to bare values. This uses
the `--spacing` variable to ensure it is safe.

   - `w-[64rem]` → `w-256`

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-05-02 23:18:06 +02:00
Philipp Spiess
45cd32eed7
Prepare v4.1.5 release (#17830)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-04-30 16:57:44 +02:00
Philipp Spiess
a636933cd4
Add discrete properties to the default list of transition properties (#17812)
This PR changes the `transition` utility to include five new properties:

- `display`
- `visibility`
- `content-visibility`
- `overlay`
- `pointer-eventes`

On its own, this change does nothing since these properties will apply
their change _immediately_. However, in combination with
`transition-discrete` this will ensure that you can now transition these
types without requiering `transition-all` or arbitrary transition
properties.

## Test plan

- Ensured this works in the Vite playground with native `<dialog>`
components


https://github.com/user-attachments/assets/89bf4a75-b681-4574-8bb4-845fffdec43b

Notice how:

- the backdrop stays open until the transition is over (that's because
of `overlay` in the property list)
- the dialog is displayed until the transition is over

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-04-30 14:30:58 +02:00
depfu[bot]
ab4eb18bab
Update @types/react-dom 19.1.1 → 19.1.2 (patch) (#17829) 2025-04-30 11:50:44 +02:00
Robin Malfait
dbc8023a08
Do not sort and format stylesheets that didn't change (#17824)
This PR improves the upgrade tooling at tiny bit to make sure that as
long as we didn't change any of the stylesheets, that we also don't sort
internal nodes and/or format the stylesheet at all.

This is important in case the Prettier rules are different or if a
totally different formatter is used.

Essentially, if we didn't have to change the stylesheets because of a
migration, we don't want to change it due to a formatter either.
2025-04-29 17:07:35 +00:00
Philipp Spiess
d3846a4570
Update test to retry assertion on empty file and don't include forward-slash in the assertion (#17821)
To fix the CI issues we have, it turns out there are two issues:

1. The file-read was not retried, making it possible for all platforms
(but mostly Windows because it's the slowest) to sometimes not have the
file created yet before making the assertion.
2. A forward-slash in the assertion message path would always be a
backslash when run on Windows, thus the Windows test would never pass.

## Test plan

 - [ci-all] and have the test pass two times.
2025-04-29 18:16:58 +02:00
depfu[bot]
9fec4ef60b
Update bun 1.2.8 → 1.2.11 (patch) (#17816) 2025-04-29 17:58:01 +02:00
Robin Malfait
d2daf59524
Skip color-mix(…) when opacity is 100% (#17815)
This PR improves colors with alpha values where the alpha value results
in 100%.

Before this change, a class like `bg-red-500/100` would be generated as:

```css
.bg-red-500\/100 {
  background-color: #ef4444;
}

@supports (color: color-mix(in lab, red, red)) {
  .bg-red-500\/100 {
    background-color: color-mix(in oklab, var(--color-red-500) 100%, transparent);
  }
}
```

But we don't need the `color-mix`, or the fallback styles at all in case
the alpha value is 100%.

After this change the `bg-red-500/100` class will generate as:

```css
.bg-red-500\/100 {
  background-color: var(--color-red-500);
}
```

Which is essentially the same as `bg-red-500`, but we can migrate that
away in the future. At least the generated CSS is smaller.

## Test plan

1. Added a test to ensure the generated value doesn't include color-mix
at all.
2025-04-28 13:30:01 -04:00
Robin Malfait
3a1b27e3f8
Pretty print variants starting with @ (#17814)
While working on another PR I noticed that some variants were re-printed
in an odd way.

Specifically, this PR fixes an issue where variants using the `@`-root
were incorrectly printed.

- `@lg` was printed as `@-lg`
- `@[400px]` was printed as `@-[400px]`

This is now special cased where the `-` is not inserted for `@`-root
variants.

## Test plan

1. Added a test to ensure the `@`-root variants are printed correctly.
2025-04-28 13:25:04 -04:00
Philipp Spiess
af1d4aa683 Temporarily disable broken Windows test 2025-04-28 10:53:46 +02:00
Jordan Pittman
ba103799f7
Add h-lh/min-h-lh/max-h-lh utilities to match an elements line height (#17790)
This PR adds the following utilities that can be used to match an
elements line height:

- `h-lh`
- `min-h-lh`
- `max-h-lh`

These are all equivalent to providing `1lh` as an arbitrary value. e.g.
`h-[1lh]`
2025-04-26 15:14:45 -04:00
Philipp Spiess
62ca1ec42d Fix Windows tests 2025-04-25 13:02:18 +02:00
Philipp Spiess
52000a30f0
PostCSS: Improve error recovery (#17754)
Closes #17295

This commit addresses an issue where the PostCSS plugin would get stuck
in an error state when processing files with e.g. invalid @apply
directives.

This change prevents the PostCSS plugin from getting stuck in an error
states particularly when the error happened inside an `@import`ed CSS
files (as these were not registered as dependencies correctly before).

## Error overlays

Some frameworks (e.g. Angular 19 or Next.js) handle errors inside
PostCSS transforms to render a nice error overlay. This works well and
gives immediate feedback that something went wrong. However, even when
dependencies are registered before an error is thrown, these frameworks
_will not consider changes to these dependencies anymore_ when an error
occurs, as you can see in this Next.js example:


https://github.com/user-attachments/assets/985c9dd7-daf8-4628-b4ad-6543ef220954

To avoid conditions where errors are not recoverable, this PR makes it
so that these overlays will no longer show up in the app and only be
logged to the output console. This will need follow-up upstream work
before we can revisit this.

## Test plan

- Tested with the repro in #17295. The error can now be recovered from.
- Tested with a Next.js app where the issue in the screencast above is
now no longer happening.
- Added an integration test for errors in `@import`-ed files
- Added a unit test for the changed `@apply` behavior.
2025-04-25 12:02:10 +02:00
depfu[bot]
231cdddb94 Update all of nextjs to version 15.3.1 2025-04-25 09:59:56 +00:00
depfu[bot]
d780a55e65
Update @playwright/test 1.51.1 → 1.52.0 (minor) (#17780) 2025-04-25 11:52:55 +02:00
Robin Malfait
2bf2b4db98
update changelog 2025-04-24 11:14:27 +02:00
Robin Malfait
46758f7c29
Bump all Tailwind CSS related dependencies during upgrade (#17763)
This PR bumps all Tailwind CSS related dependencies when running the
upgrade tool _if_ the dependency exists in your package.json file.

E.g.: if you have `tailwindcss` and `@tailwindcss/vite` in your
package.json, then both will be updated to the latest version.

This PR is not trying to be smart and skip updating if you are already
on the latest version. It will just try and update the dependencies and
your package manager will do nothing in case it was already the latest
version.

## Test plan

Ran this on one of my personal projects and this was the output:
<img width="1023" alt="image"
src="https://github.com/user-attachments/assets/a189fe7a-a58a-44aa-9246-b720e7a2a892"
/>


Notice that I don't have `@tailwindcss/vite` logs because I am using
`@tailwindcss/postcss`.
2025-04-24 11:13:21 +02:00
Philipp Spiess
a7f4a4d3b8
Upgrade wasm related dependencies (#17753)
Closes #17752
Closes #17748
Closes #17747
Closes #17746
Closes #17745
2025-04-23 15:15:06 +02:00
Robin Malfait
8e826b18f3
Ensure @tailwindcss/upgrade runs on Tailwind CSS v4 projects and is idempotent (#17717)
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 from
`node_modules`). Not the one available in your package.json. Your
`package.json` could be out of sync if you reverted changes but didn't
run `npm 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:

| Before      | After       |
| ----------- | ----------- |
| `shadow`    | `shadow-sm` |
| `shadow-sm` | `shadow-xs` |

Notice how we have `shadow-sm` in both the `before` and `after` column.

If we would run the upgrade tool again, then we would eventually migrate
your original `shadow` to `shadow-sm` (first run) and then to
`shadow-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.

1. Find CSS files
2. Link JS config files (if you are in a Tailwind CSS v3 project)
3. Migrate the JS config files (if you are in a Tailwind CSS v3 project)
4. Upgrade Tailwind CSS to v4 (or the latest version at that point)
5. Migrate the stylesheets (we used to migrate the source files first)
6. Migrate the source files

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.

1. We migrate deprecated classes to their new names:

   | Before                | After                 |
   | --------------------- | --------------------- |
   | `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:

   - https://github.com/tailwindlabs/tailwindcss/pull/17378
   - https://github.com/tailwindlabs/tailwindcss/pull/17437

2. We migrate simple arbitrary variants to their dedicated variant:

   | Before                  | After               |
   | ----------------------- | ------------------- |
   | `[&:user-valid]:flex`   | `user-valid:flex`   |
   | `[&:user-invalid]:flex` | `user-invalid:flex` |

   Introduced in:

   - https://github.com/tailwindlabs/tailwindcss/pull/12370

3. We migrate `@media` variants to their dedicated variant:

| Before | After |
| ----------------------------------------------------- |
------------------------- |
| `[@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`
and `scripting` were introduced in:

   - https://github.com/tailwindlabs/tailwindcss/pull/11693
   - https://github.com/tailwindlabs/tailwindcss/pull/16946
   - https://github.com/tailwindlabs/tailwindcss/pull/11929
   - https://github.com/tailwindlabs/tailwindcss/pull/17431

   This also applies to the `not` case, e.g.:

| Before | After |
| --------------------------------------------------------- |
----------------------------- |
| `[@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:

| Before                 | After             |
| ---------------------- | ----------------- |
| `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.:

| Before | After | Reason |
| ----------------------- | ---------------------- |
------------------------------------ |
| `!bg-[var(--my-color)]` | `bg-(--my-color)!` | Because the `!` is in
the wrong spot |
| `bg-[var(--my-color)]` | `bg-[var(--my-color)]` | Because no
migrations rand |

As you can see, the way the `--my-color` variable is used, is different.
This
changes will make sure it will now always be consistent:
| Before | After |
| ----------------------- | ---------------------- |
| `!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

- All existing tests still pass.
- But I had to delete 1 test (we tested that Tailwind CSS v3 was
required).
- And had to mock the `version.isMajor` call to ensure we run the
individual migration tests correctly.
- Added new tests to test:
  1. Migrating Tailwind CSS v4 projects works
  1. Idempotency of the upgrade tool

[ci-all]
2025-04-22 11:10:46 -04:00
Robin Malfait
25ec6a3b7d
Ignore .db files by default (#17711)
This PR ignores `.db` files by default. We already ignored `.sqlite` and
`.sqlite3` files but we didn't ignore `.db` files which is a common
extension for database files as well.

Due to the binary nature of `.db` files, scanning these could result in
hard to debug errors such as:


![image](https://github.com/user-attachments/assets/52f779ac-cd5f-4f37-9615-2163bf852999)
2025-04-22 14:53:11 +00:00
Jordan Pittman
8bf06ab770
Handle legacy key behavior in theme-driven suggestions for @utility (#17733)
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1328

The alpha and beta releases used `_` in theme keys to represent a `.`.
This meant we used `--leading-1_5` instead of `--leading-1\.5` to add
utilities like `leading-1.5`. We prefer the use of the escaped dot now
but still want to make sure suggestions for the legacy key format still
works as expected when surrounded by numbers.

This is the same as #16433 but for `@utility` since we apparently missed
this when emitting suggestions for it
2025-04-22 10:02:28 -04:00
Jordan Pittman
650558df5e Update changelog 2025-04-22 09:31:25 -04:00
Jordan Pittman
ee0d7525d8
Hide default shadow suggestions when missing theme keys (#17743)
Right now if you have a completely empty theme we'll still suggest
`shadow`, `inset-shadow`, and `text-shadow` as utilities even tho they
won't exist. This fixes this by checking for the theme key when
computing the suggestions.
2025-04-22 09:30:19 -04:00
depfu[bot]
fc4afc2538
Update @types/react 19.0.12 → 19.1.2 (minor) (#17738) 2025-04-22 10:54:44 +02:00
Adam Wathan
8feb6a758a
Ignore .geojson files by default (#17700)
Resolves https://github.com/tailwindlabs/tailwindcss/issues/17699

GeoJSON files are giant JSON files for geographic data structures and
will never contain Tailwind classes, but because they are often huge
they will slow down the build a lot if scanned.

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2025-04-18 10:46:33 +02:00
depfu[bot]
adcf1dede2
Update turbo 2.4.4 → 2.5.0 (minor) (#17678) 2025-04-15 12:03:19 +02:00
Philipp Spiess
25539e3533 Fix release script 2025-04-14 19:06:18 +02:00
Philipp Spiess
aa836d3442
Prepare v4.1.4 release (#17669)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-04-14 17:32:30 +02:00
depfu[bot]
cf2591c44b
Update eslint 9.22.0 → 9.24.0 (minor) (#17656)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-04-14 11:13:15 +02:00
Robin Malfait
bbd916aaa0
Ignore binary extensions, except in folder names (#17595)
We generate a glob to ignore binary extensions that looks something like
this:
```
*.{mp4,pages,exe,…}
```

However, if you have a folder that happens to end in `.pages` for
example, then this folder will be ignored in its entirety.

To solve this, we added a new flag to the `Gitignore` struct so we can
register a bunch of ignore rules that _only_ apply to paths and not
folders.

Fixes: #17569

## Test plan

- Added a unit test

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-04-12 13:30:17 +02:00
Philipp Spiess
d801d8dc54
Fix publish step on CI (#17654)
To publish the newly adde WASM builds, we rely on the
`node-linker=hoisted` `.npmrc` flag that isn't read when `pnpm
--recursive` is used. To work around it, this PR excludes the wasm
package from the `--recursive` part and manually published it
afterwards.


## Test Plan

Ensured this does not error now when trying a `--dry run`.

<img width="1273" alt="Screenshot 2025-04-11 at 17 43 38"
src="https://github.com/user-attachments/assets/68a28552-0125-4da1-92ff-74e58368abe4"
/>
2025-04-11 18:14:48 +02:00
Philipp Spiess
83ce4c0a30
Add experimental @tailwindcss/oxide-wasm32-wasi (#17558)
Closes #17448
Closes #13133

This PR adds an a new Oxide target for `wasm32-wasip1-threads`:
`@tailwindcss/oxide-wasm32-wasi`. The goal of this is to enable more
environments to run Oxide, including (but not limited to) StackBlitz.

We're making use of `napi-rs`'s upcoming v3 features to simplify the
setup here, meaning `napi-rs` will configure the WASM target and create
an npm package that works across Node and browser environments.

## MacOS AArch64 issues

While setting up an integration test for the new WASM target, I ran into
an issue where FS reads where not terminating on macOS. After some
research I found this to be a limitation of the Node.js container
interface right now, see: https://github.com/nodejs/node/issues/47193

### Windows issues

We also found that the Node.js wasi container does not properly support
Windows: https://github.com/nodejs/uvwasi/issues/11

For now we, it's probably best for MacOS AArch64 users and Windows users
to use the native modules instead.

## Test plan

The `@tailwindcss/oxide-wasm32-wasi` npm package can be built locally
via `pnpm build` and then run with the Oxide API. A usage example can be
taken from the newly added integration test.

Furthermore this was tested to work as a polyfill on StackBlitz:
https://stackblitz.com/edit/vitejs-vite-uks3gt5p

[ci-all]

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-04-11 17:19:55 +02:00
Philipp Spiess
6e1f53348d
Workaround Chrome rendering bug for skew-* utilities (#17627)
Closes #17621

Chrome as a pretty ugly rendering glitch when using a `skew-*` utility
in Tailwind 4: https://play.tailwindcss.com/HuiZtbrHOc

The was not an issue in v3 since transforms were set up in a different
way. Without the `var(…)` syntax, the difference boils down to this:

```css
.skew-v3 {
  transform: rotate(0) skewX(-20deg);
}

.skew-v4 {
  transform: rotateX(0) rotateY(0) rotateZ(0) skewX(-20deg);
}
```

It appears that using any of the single-dimension rotate functions will
cause the Chrome rendering to glitch.

After doing some digging, we found [that initially these `@property`s
were defined as type `<transform-function>` and later changed to
`*`](https://github.com/tailwindlabs/tailwindcss/issues/15144). With a
type of `*`, it makes sense that the initial value of these variables
can default to `initial` without any compromises, allowing us to default
to something like this now:

```css
.skew-new {
  transform: skewX(-20deg);
}
```

Tested this change in the latest version of Chrome (135) and it does
make the rendering glitch in the initial issue disappear. By using the
`var(--tw-rotate-x,)` syntax we also ensure this works on older versions
of Safari (tested on Safari 15.5 and 16.4).

Note, however, that there are still glitches happening when you combine
rotate and skew in the latest version of Chrome or when you transition
the `skew(…)` variable. This also happens in plain CSS with no variables
though, so there isn't something we can do about this:
https://play.tailwindcss.com/g3FXPEJHpn

## Test plan

- Tested on latest Chrome, Firefox, and Safari as well as Safari 15.5
and 16.4.

<img width="564" alt="Screenshot 2025-04-09 at 18 01 51"
src="https://github.com/user-attachments/assets/2e0b1c96-7c4d-41a8-b3d0-0f6134a3e635"
/>
2025-04-11 16:38:46 +02:00
Scott Bedard
3bea760ff2
Add test coverage for property-specific colors (#17436)
There are several property-specific color variables available to
maintain compatibility with v3. [My team and I would like to use
them](https://github.com/tailwindlabs/tailwindcss/discussions/17400),
but would feel more comfortable if they were formally supported by v4.
This PR adds test coverage for those features.

@RobinMalfait has confirmed that these features are not intended to be
removed, [see conversation here
&rarr;](1355213322)

@crswll Has opened a PR here for the related documentation
- https://github.com/tailwindlabs/tailwindcss.com/pull/2178
2025-04-11 13:40:10 +02:00
Justin Wong
6d8dd82c40
Fix shadow-inherit, inset-shadow-inherit, drop-shadow-inherit, and text-shadow-inherit (#17647)
Fixes #17643.

This PR completely removes the `color-mix()` function for
`shadow-inherit`. This does mean intensity and alpha channel support has
been removed when using `shadow-inherit`[^1].

With intensity modifiers in #17398, all colors are wrapped in
`color-mix()`. However, it seems `inherit` does not work as a value in
`color-mix()` in Firefox or Chrome (don't have a means to test Safari).

[^1]: While writing this, I noticed other color utilities allow alpha
channel modifier syntax for `inherit` - do we want to look at removing
those too?

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-04-11 13:36:18 +02:00
Justin Wong
c0af1e2129
Fix fontSize array upgrade (#17630)
Fixes #17622.

Adds a specific handling case in `themeableValues()` in
`packages/tailwindcss/src/compat/apply-config-to-theme.ts`. It seems
like this has unique handling in v3 for an array value, whereby the
second value is treated as a `line-height`.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-04-11 13:23:54 +02:00
Justin Wong
3ab7f12563
Fix container names with hyphens (#17628)
Fixes #17614.

Candidate parsing for variants only account for the root `@` if there no
hyphens. It seems like the current logic assumes if it *does* have a
hyphen, then it would be one of `@min` or `@max`. However, with:

```css
@theme {
  --container-foo-bar: 1440px;
}
```
Then `@foo-bar` should be valid. However, we only check for `@foo-bar`
and `@foo` as roots, but never `@`. This PR adds a check for `@` at the
very end after iterating through root permutations.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-04-11 12:28:25 +02:00
depfu[bot]
cdecb55702
Update @types/react-dom 19.0.4 → 19.1.1 (minor) (#17619) 2025-04-09 10:26:51 +02:00
Robin Malfait
f66d287436
Fix brace expansion with range going down (#17591) 2025-04-07 15:48:19 +02:00
depfu[bot]
76e18e679c
Update all of react 19.0.0 → 19.1.0 (minor) (#17564)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ react (19.0.0 → 19.1.0) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.1.0">19.1.0</a></h4>

<blockquote><h3 dir="auto">Owner Stack</h3>
<p dir="auto">An Owner Stack is a string representing the components
that are directly responsible for rendering a particular component. You
can log Owner Stacks when debugging or use Owner Stacks to enhance error
overlays or other development tools. Owner Stacks are only available in
development builds. Component Stacks in production are unchanged.</p>
<ul dir="auto">
<li>An Owner Stack is a development-only stack trace that helps identify
which components are responsible for rendering a particular component.
An Owner Stack is distinct from a Component Stacks, which shows the
hierarchy of components leading to an error.</li>
<li>The <a
href="https://react.dev/reference/react/captureOwnerStack">captureOwnerStack
API</a> is only available in development mode and returns a Owner Stack,
if available. The API can be used to enhance error overlays or log
component relationships when debugging. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/29923">#29923</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32353">#32353</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/30306">#30306</a>,<br>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32538">#32538</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32529">#32529</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32538">#32538</a>
</li>
</ul>
<h3 dir="auto">React</h3>
<ul dir="auto">
<li>Enhanced support for Suspense boundaries to be used anywhere,
including the client, server, and during hydration. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32069">#32069</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32163">#32163</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32224">#32224</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32252">#32252</a>
</li>
<li>Reduced unnecessary client rendering through improved hydration
scheduling <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31751">#31751</a>
</li>
<li>Increased priority of client rendered Suspense boundaries <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31776">#31776</a>
</li>
<li>Fixed frozen fallback states by rendering unfinished Suspense
boundaries on the client. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31620">#31620</a>
</li>
<li>Reduced garbage collection pressure by improving Suspense boundary
retries. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31667">#31667</a>
</li>
<li>Fixed erroneous “Waiting for Paint” log when the passive effect
phase was not delayed <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31526">#31526</a>
</li>
<li>Fixed a regression causing key warnings for flattened positional
children in development mode. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32117">#32117</a>
</li>
<li>Updated <code class="notranslate">useId</code> to use valid CSS
selectors, changing format from <code class="notranslate">:r123:</code>
to <code class="notranslate">«r123»</code>. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32001">#32001</a>
</li>
<li>Added a dev-only warning for null/undefined created in useEffect,
useInsertionEffect, and useLayoutEffect. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32355">#32355</a>
</li>
<li>Fixed a bug where dev-only methods were exported in production
builds. React.act is no longer available in production builds. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32200">#32200</a>
</li>
<li>Improved consistency across prod and dev to improve compatibility
with Google Closure Complier and bindings <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31808">#31808</a>
</li>
<li>Improve passive effect scheduling for consistent task yielding. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31785">#31785</a>
</li>
<li>Fixed asserts in React Native when
passChildrenWhenCloningPersistedNodes is enabled for OffscreenComponent
rendering. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32528">#32528</a>
</li>
<li>Fixed component name resolution for Portal <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32640">#32640</a>
</li>
<li>Added support for beforetoggle and toggle events on the dialog
element. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32479">#32479</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32479">#32479</a>
</li>
</ul>
<h3 dir="auto">React DOM</h3>
<ul dir="auto">
<li>Fixed double warning when the <code class="notranslate">href</code>
attribute is an empty string <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31783">#31783</a>
</li>
<li>Fixed an edge case where <code
class="notranslate">getHoistableRoot()</code> didn’t work properly when
the container was a Document <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32321">#32321</a>
</li>
<li>Removed support for using HTML comments (e.g. <code
class="notranslate">&lt;!-- --&gt;</code>) as a DOM container. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32250">#32250</a>
</li>
<li>Added support for <code class="notranslate">&lt;script&gt;</code>
and <code class="notranslate">&lt;template&gt;</code> tags to be nested
within <code class="notranslate">&lt;select&gt;</code> tags. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31837">#31837</a>
</li>
<li>Fixed responsive images to be preloaded as HTML instead of headers
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32445">#32445</a>
</li>
</ul>
<h3 dir="auto">use-sync-external-store</h3>
<ul dir="auto">
<li>Added <code class="notranslate">exports</code> field to <code
class="notranslate">package.json</code> for <code
class="notranslate">use-sync-external-store</code> to support various
entrypoints. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/25231">#25231</a>
</li>
</ul>
<h3 dir="auto">React Server Components</h3>
<ul dir="auto">
<li>Added <code class="notranslate">unstable_prerender</code>, a new
experimental API for prerendering React Server Components on the server
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31724">#31724</a>
</li>
<li>Fixed an issue where streams would hang when receiving new chunks
after a global error <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31840">#31840</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31851">#31851</a>
</li>
<li>Fixed an issue where pending chunks were counted twice. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31833">#31833</a>
</li>
<li>Added support for streaming in edge environments <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31852">#31852</a>
</li>
<li>Added support for sending custom error names from a server so that
they are available in the client for console replaying. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32116">#32116</a>
</li>
<li>Updated the server component wire format to remove IDs for hints and
console.log because they have no return value <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31671">#31671</a>
</li>
<li>Exposed <code class="notranslate">registerServerReference</code> in
client builds to handle server references in different environments. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32534">#32534</a>
</li>
<li>Added react-server-dom-parcel package which integrates Server
Components with the <a href="https://parceljs.org/">Parcel bundler</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31725">#31725</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32132">#32132</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31799">#31799</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32294">#32294</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31741">#31741</a>
</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="7aa5dda3b3...4a9df08157">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>




#### ✳️ react-dom (19.0.0 → 19.1.0) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.1.0">19.1.0</a></h4>

<blockquote><h3 dir="auto">Owner Stack</h3>
<p dir="auto">An Owner Stack is a string representing the components
that are directly responsible for rendering a particular component. You
can log Owner Stacks when debugging or use Owner Stacks to enhance error
overlays or other development tools. Owner Stacks are only available in
development builds. Component Stacks in production are unchanged.</p>
<ul dir="auto">
<li>An Owner Stack is a development-only stack trace that helps identify
which components are responsible for rendering a particular component.
An Owner Stack is distinct from a Component Stacks, which shows the
hierarchy of components leading to an error.</li>
<li>The <a
href="https://react.dev/reference/react/captureOwnerStack">captureOwnerStack
API</a> is only available in development mode and returns a Owner Stack,
if available. The API can be used to enhance error overlays or log
component relationships when debugging. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/29923">#29923</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32353">#32353</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/30306">#30306</a>,<br>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32538">#32538</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32529">#32529</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32538">#32538</a>
</li>
</ul>
<h3 dir="auto">React</h3>
<ul dir="auto">
<li>Enhanced support for Suspense boundaries to be used anywhere,
including the client, server, and during hydration. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32069">#32069</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32163">#32163</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32224">#32224</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32252">#32252</a>
</li>
<li>Reduced unnecessary client rendering through improved hydration
scheduling <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31751">#31751</a>
</li>
<li>Increased priority of client rendered Suspense boundaries <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31776">#31776</a>
</li>
<li>Fixed frozen fallback states by rendering unfinished Suspense
boundaries on the client. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31620">#31620</a>
</li>
<li>Reduced garbage collection pressure by improving Suspense boundary
retries. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31667">#31667</a>
</li>
<li>Fixed erroneous “Waiting for Paint” log when the passive effect
phase was not delayed <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31526">#31526</a>
</li>
<li>Fixed a regression causing key warnings for flattened positional
children in development mode. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32117">#32117</a>
</li>
<li>Updated <code class="notranslate">useId</code> to use valid CSS
selectors, changing format from <code class="notranslate">:r123:</code>
to <code class="notranslate">«r123»</code>. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32001">#32001</a>
</li>
<li>Added a dev-only warning for null/undefined created in useEffect,
useInsertionEffect, and useLayoutEffect. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32355">#32355</a>
</li>
<li>Fixed a bug where dev-only methods were exported in production
builds. React.act is no longer available in production builds. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32200">#32200</a>
</li>
<li>Improved consistency across prod and dev to improve compatibility
with Google Closure Complier and bindings <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31808">#31808</a>
</li>
<li>Improve passive effect scheduling for consistent task yielding. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31785">#31785</a>
</li>
<li>Fixed asserts in React Native when
passChildrenWhenCloningPersistedNodes is enabled for OffscreenComponent
rendering. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32528">#32528</a>
</li>
<li>Fixed component name resolution for Portal <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32640">#32640</a>
</li>
<li>Added support for beforetoggle and toggle events on the dialog
element. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32479">#32479</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32479">#32479</a>
</li>
</ul>
<h3 dir="auto">React DOM</h3>
<ul dir="auto">
<li>Fixed double warning when the <code class="notranslate">href</code>
attribute is an empty string <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31783">#31783</a>
</li>
<li>Fixed an edge case where <code
class="notranslate">getHoistableRoot()</code> didn’t work properly when
the container was a Document <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32321">#32321</a>
</li>
<li>Removed support for using HTML comments (e.g. <code
class="notranslate">&lt;!-- --&gt;</code>) as a DOM container. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32250">#32250</a>
</li>
<li>Added support for <code class="notranslate">&lt;script&gt;</code>
and <code class="notranslate">&lt;template&gt;</code> tags to be nested
within <code class="notranslate">&lt;select&gt;</code> tags. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31837">#31837</a>
</li>
<li>Fixed responsive images to be preloaded as HTML instead of headers
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32445">#32445</a>
</li>
</ul>
<h3 dir="auto">use-sync-external-store</h3>
<ul dir="auto">
<li>Added <code class="notranslate">exports</code> field to <code
class="notranslate">package.json</code> for <code
class="notranslate">use-sync-external-store</code> to support various
entrypoints. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/25231">#25231</a>
</li>
</ul>
<h3 dir="auto">React Server Components</h3>
<ul dir="auto">
<li>Added <code class="notranslate">unstable_prerender</code>, a new
experimental API for prerendering React Server Components on the server
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31724">#31724</a>
</li>
<li>Fixed an issue where streams would hang when receiving new chunks
after a global error <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31840">#31840</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31851">#31851</a>
</li>
<li>Fixed an issue where pending chunks were counted twice. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31833">#31833</a>
</li>
<li>Added support for streaming in edge environments <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31852">#31852</a>
</li>
<li>Added support for sending custom error names from a server so that
they are available in the client for console replaying. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32116">#32116</a>
</li>
<li>Updated the server component wire format to remove IDs for hints and
console.log because they have no return value <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31671">#31671</a>
</li>
<li>Exposed <code class="notranslate">registerServerReference</code> in
client builds to handle server references in different environments. <a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32534">#32534</a>
</li>
<li>Added react-server-dom-parcel package which integrates Server
Components with the <a href="https://parceljs.org/">Parcel bundler</a>
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31725">#31725</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32132">#32132</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31799">#31799</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/32294">#32294</a>,
<a
href="https://bounce.depfu.com/github.com/facebook/react/pull/31741">#31741</a>
</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="7aa5dda3b3...4a9df08157">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-04-07 13:05:56 +00:00
Philipp Spiess
3e9cf87adf
Make polyfill work when the theme variable resolves to another var (#17562)
Discovered while triaging #17556

This PR improves the `color-mix(...)` polyfill to ensure it works when a
theme key links to another theme key via a `var(...)` call.

Imagine this setup:

```css
 @theme {
  --color-red: var(--color-red-500);
  --color-red-500: oklch(63.7% 0.237 25.331);
}
@source inline("text-red/50");
````

Since `--color-red` will link to `--color-red-500` _which is also a
known theme variable_, we can inline the value of `--color-red-500` into
the fallback now:

```css
.text-red\\/50 {
  color: var(--color-red);
}
@supports (color: color-mix(in lab, red, red)) {
  .text-red\\/50 {
    color: color-mix(in oklab, var(--color-red) 50%, transparent);
  }
}
```

This will allow for slightly less confusing behavior.

The code added also handles recursive definitions where a color is
linking to another color that is again linking to the first one (by
adding a `Set` to keep track of already seen variable names).

## Test plan

- Added unit test
2025-04-07 11:42:02 +02:00
Philipp Spiess
811e97d61a
Fix polyfill in combination with unused CSS variable removal (#17555)
This PR fixes an issue we noticed while investigating #17553, where the
unused CSS variable removal didn't work properly when the theme variable
it tried to remove was modified by a polyfill rule.

The way the bookkeeping for the unused CSS variable worked was that it
tired to find the declaration inside it's parent after the traversal.
However, the `color-mix(…)` polyfill has since then made changes to the
declaration so it can't find it's position correctly anymore and will
thus instead delete the last declaration of the node (this caused
unrelated CSS variables to be eliminated while the ones with
`color-mix(…)` were unexpectedly kept).

To fix this, we decided to apply the polyfills after any eventual
deletions. This also ensures that no `@supports` query for the variables
are created and simplifies the code a bit since all polyfills are now
colocated.

## Test plan

- Added a unit test for the example we discovered in #17553
- Luckily the conditions of this seemed rare enough so that it doesn't
cause any other of our tests to update.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-04-07 09:18:47 +00:00
Robin Malfait
5a77c9dfc4
Prepare v4.1.3 release (#17563)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-04-04 19:54:23 +02:00
Robin Malfait
7d317251f2
Handle Ruby %w syntax in Slim pre processing (#17557)
This PR fixes an issue where the Ruby `%w[…]` syntax causes utilities to
not be properly extracted.

This PR will now ensure that it does get extracted correctly.

Given this input:
```slim
div[
  class=%w[bg-blue-500 w-10 h-10]
]
div[
  class=%w[w-10 bg-green-500 h-10]
]
```

Before this PR, we extracted everything but the `bg-blue-500`. The
`w-10` was extracted but not because of the second div, but because of
the first one.

Fixes: #17542

## Test plan

1. Added a test to ensure it's working correctly.

Looking at the extractor tool, you can see that it now gets extracted
correctly. Top is before, bottom is with this change.

<img width="1199" alt="image"
src="https://github.com/user-attachments/assets/028d9abd-8917-438c-a423-88ba887b7f97"
/>
2025-04-04 17:21:35 +02:00
Philipp Spiess
e085977844
PostCSS: Fix Turbopack 'one-revision-behind' bug (#17554)
Closes #17508

This PR fixes another issue we found that caused dev builds with Next.js
and Turbopack to resolve the CSS file that was saved one revision before
the latest update.

When debugging this we noticed that the PostCSS entry is called twice
for every one update when changing the input CSS file directly. That was
caused by the input file itself being added as a _dependency_ so you
would first get the callback that a _dependency_ has updated (at which
point we look at the file system and figure out we need a full-rebuild
because the input.css file has changed) and then another callback for
when the _input file_ has updated. The problem with the second callback
was that the file-system was already scanned for updates and since this
includes the `mtimes` for the input file, we seemingly thought that the
input file did not change. However, the issue is that the first callback
actually came with an outdated PostCSS input AST...

We found that this problem arises when you register the input CSS as a
dependency of itself. This is not expected and we actually guard against
this in the PostCSS client. However, we found that the input `from`
argument is _a relative path when using Next.js with Turbopack_ so that
check was not working as expected.

## Test plan

Added the change to the repro from #17508 and it seems to work fine now.


https://github.com/user-attachments/assets/2acb0078-f961-4498-be1a-b1c72d5ceda1

Also added a unit test to ensure we document that the input file path
can be a relative path.

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-04-04 14:58:50 +02:00
Robin Malfait
2fd7c8d967
Show warning when using unsupported bare value data type (#17464)
This PR will show a warning if you are using a bare value data type that
is not supported.

Let's say you want to create a new utility that allows `color` to be a
bare value data type like this:
```css
@utility paint-* {
  paint: --value([color], color);
}
```

This means that this would enable new syntax that we don't support yet.
E.g.: `paint-#0088cc`.

The only supported data types for bare values are:

- `number` — `2.5`
- `integer` — `2`
- `ratio` — `1/2`
- `percentage` — `50%`

All other data types are not supported in this position. This PR will
now show a warning:
~~~
Unsupported bare value data type: "color".
Only valid data types are: "number", "integer", "ratio", "percentage".

```css
--value([color],color)
                ^^^^^
```
~~~
Once we have better sourcemap / location tracking support, this warning
will point to the exact spot, but for now, only a re-print of the AST
can be used.


If you _do_ want to use other data types, then you will have to use
arbitrary value syntax with `[…]` instead.


```css
@utility paint-* {
  paint: --value([color]);
}
```

This will allow for `paint-[#0088cc]` for example.

Note: this is not a behavioral change, we already didn't support other
data types, but we silently ignored them. This means that we have to do
more parsing at runtime when evaluating the utility.

With this change, a warning is shown when registering the `@utility`,
not when using it.
2025-04-04 12:20:46 +00:00
depfu[bot]
57e55a6fe4
Update @playwright/test 1.51.0 → 1.51.1 (patch) (#17538) 2025-04-04 10:57:54 +02:00
Robin Malfait
fc94ab4727
update changelog 2025-04-03 19:14:18 +02:00
Philipp Spiess
3f434a6f00
Vite: Don't register the current CSS file as a dependency on itself (#17533)
Closes #17512

One of the changes of the Oxide API in 4.1 is that it now emits the
input CSS file itself as a dependency. This was fine in most of our
testing but it turns out that certain integrations (in this case a Qwik
project) don't like this and will silently crash with no CSS file being
added anymore.

This PR fixes this by making sure we don't add the input file as a
dependency on itself and also adds an integration test to ensure this
won't regress again.

## Test plan

- Tested with the repro provided in #17512
- Added a minimal integration test based on that reproduction that I
also validated will _fail_, if the fix is reverted.
2025-04-03 18:50:16 +02:00
Robin Malfait
4c99367b7b
Prepare release v4.1.2 (#17530)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-04-03 15:36:42 +00:00
Philipp Spiess
5a9d1f4d5c
Fix test that relies on mtimes (#17529)
Fixes a timing issue added to a new unit test on `main`. Going to wait
for the `CI / Linux` unit tests to pass 3 times before merging this.
2025-04-03 15:24:26 +00:00
Philipp Spiess
4200a1ecc4
Fix slow incremental builds (especially on Windows) (#17511)
This PR fixes slow rebuilds on Windows where rebuilds go from ~2s to
~20ms.

Fixes: #16911
Fixes: #17522

## Test plan

1. Tested it on a reproduction with the following results:

Before:


https://github.com/user-attachments/assets/10c5e9e0-3c41-4e1d-95f6-ee8d856577ef

After:


https://github.com/user-attachments/assets/2c7597e9-3fff-4922-a2da-a8d06eab9047

Zooming in on the times, it looks like this:
<img width="674" alt="image"
src="https://github.com/user-attachments/assets/85eee69c-bbf6-4c28-8ce3-6dcdad74be9c"
/>

But with these changes:
<img width="719" alt="image"
src="https://github.com/user-attachments/assets/d89cefda-0711-4f84-bfaf-2bea11977bf7"
/>

We also tested this on Windows with the following results:
Before:
<img width="961" alt="image"
src="https://github.com/user-attachments/assets/3a42f822-f103-4598-9a91-e659ae09800c"
/>

After:
<img width="956" alt="image"
src="https://github.com/user-attachments/assets/05b6b6bc-d107-40d1-a207-3638aba3fc3a"
/>


[ci-all]

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-04-03 17:13:15 +02:00
Philipp Spiess
60b0da90ce
Polyfill: Fall back to first color value when color-mix(…) contains unresolvable var(…) (#17513)
This PR further improves the `color-mix(…)` polyfill to create a
reasonable fallback if dynamic values that can not statically be
resolved are used. This refers to either the use of `currentcolor` or
any variables that are not static theme variables.

Here are two examples that now generate a reasonable fallback instead of
not showing any color at all:

```css
.text-\\(--my-color\\)\\/\\(--my-opacity\\) {
  color: var(--my-color);
}
@supports (color: color-mix(in lab, red, red)) {
  .text-\\(--my-color\\)\\/\\(--my-opacity\\) {
    color: color-mix(in oklab, var(--my-color) var(--my-opacity), transparent);
  }
}
```

```css
.text-current\\/50 {
  color: currentColor;
}

@supports (color: color-mix(in lab, red, red)) {
  .text-current\\/50 {
    color: color-mix(in oklab, currentColor 50%, transparent);
  }
}
```

## Test plan

- Made sure the test diffs are looking reasonable
- Tested this on a production site with `<p className="text-shadow-lg/50
[--my-color:red] text-shadow-(color:--my-color)">shadow test</p>`
- Browsers that do not support `color-mix(…)` will properly show a red
shadow now albeit with 100% opacity: iOS 15.5 and Chrome 110
- Browsers that I have tested to make sure it still works there with
opacity: Firefox 127, Firefox 128, Latest Chrome, Safari, Firefox
- Browsers that do show a black shadow because of `var(…)var(…)` being
chained with no space by lightningcss: Chrome 111
2025-04-03 15:12:34 +00:00
Robin Malfait
81a676f129
Fix race condition in Next.js with --turbopack (#17514)
This PR fixes an issue where if you use Next.js with `--turbopack` a
race condition happens because the `@tailwindcss/postcss` plugin is
called twice in rapid succession.

The first call sees an update and does a partial update with the new
classes. Next some internal `mtimes` are updated. The second call
therefore doesn't see any changes anymore because the `mtimes` are the
same, therefore it's serving its stale data.

Fixes: #17508

## Test plan

- Tested with the repro provided in #17508
- Added a new unit test that calls into the PostCSS plugin directly for
the same change from the same JavaScript run-loop.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-04-03 17:07:38 +02:00
Jordan Pittman
e45302b910
Fix drop shadow filters with multiple shadows (#17515)
It seems that I broke support for multiple drop-shadow filters when
`@theme inline` was used in v4.1. This PR fixes that by segmenting the
drop shadow value on top-level commas and wrapping each segment with
`drop-shadow(…)` like we did in v4.0.
2025-04-03 14:37:56 +00:00
Teddy Bradford
3e41e9ffe6
Replace currentColor with currentcolor (lowercase) (#17510)
Replaces `currentColor` with `currentcolor` (lowercase) to match what's
defined in [CSS Color Module Level
4](https://www.w3.org/TR/css-color-4/#currentcolor-color) and
[MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentcolor_keyword)
(see: https://github.com/mdn/content/pull/16592).

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-04-03 16:09:12 +02:00
depfu[bot]
f8b9aa983a
Update @types/bun 1.2.4 → 1.2.8 (patch) (#17528) 2025-04-03 16:05:41 +02:00
depfu[bot]
e5b2b0f12c
Update bun 1.2.4 → 1.2.8 (patch) (#17527) 2025-04-03 15:53:27 +02:00
Justin Wong
80f9578bfa
Fix multi-value arbitrary inset shadow (#17523)
Fixes #17520

Fixes multi-value inset shadows to apply the `inset` prefix to each
component instead of only the first.

Feel free to make the code nicer 😄

## Test plan

Ensure that a multi-value inset shadow now applies each shadow _inset_: 
<img width="505" alt="Screenshot 2025-04-03 at 10 50 29"
src="https://github.com/user-attachments/assets/5d38de45-a16f-48fd-8e3c-b50d2740eb49"
/>

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-04-03 11:15:30 +02:00
Philipp Spiess
4484192ca3
Use @layer properties for @property polyfills (#17506)
This PR changes how polyfills for `@property` are inserted. The main
motivation is to remove the need to rely on the correct placement of
`@layer base;`—Something that's not really required right not in
Tailwind CSS v4 and we'd like to keep it this way.

The idea is that the polyfills are inserted for you automatically. To
ensure they always take precedence, we insert an empty `@layer
properties;` at the top of the CSS file so that later, when we emit all
`@property` rules and their fallback, we can use this new named layer to
ensure the rules have a higher order.

Unfortunately, just putting `@layer properties;` at the beginning of a
file would not work as `lightningcss` incorrectly hoists all content
into the first occurrence of a layer name meaning these rules might be
inserted _before_ eventual external imports:


![image](https://github.com/user-attachments/assets/c5a1694d-1549-47ed-ad0f-266807be4730)

To work around this, we have to insert that layer name after any
eventual remaining external `@imports` for now.

## Test plan

- Updated snapshot tests
- Deployed a new version of the website with the patch applied to ensure
it works across browsers:
https://tailwindcss-com-git-legacy-browsers-tailwindlabs.vercel.app/.
Tested on: Safari on iOS 15.5, Safari on iOS 16.0, Firefox 127, Firefox
128, Chrome 110, Chrome latest, Safari latest, Firefox latest
2025-04-02 18:16:28 +02:00
Robin Malfait
6a0a3ec0fa
Prepare release v4.1.1 (#17503) 2025-04-02 09:27:58 +00:00
Philipp Spiess
a429462639
Use @tailwindcss/node for import in CLI (#17502)
Closes #17501

Seems like an oversight. The CLI does have a dependency on
`@tailwindcss/node` so it should use it from the public import like the
other stuff.
2025-04-02 09:16:58 +00:00
Robin Malfait
3c937ecee7
Inject polyfills after @import and body-less @layer (#17493)
This PR fixes an issue where polyfills were injected at the top, but
they should be after `@import` and body-less `@layer` rules.

This is necessary in case you are using Google fonts like this for
example:
```css
@import url('https://fonts.google.com');
@import "tailwindcss";
```

While the `@import url(…);` sits above `@import "tailwindcss";` in the
final generated CSS we injected the polyfills at the very beginning.

This PR will inject the polyfills after the first AST Node that is not:
1. A comment
2. An external import — `@import url(…)`
3. A body-less layer — `@layer foo, bar, baz;`

The snapshots look a little confusing, but that's because Lightning CSS
is optimizing the output and moving things around a bit:

<img width="1482" alt="image"
src="https://github.com/user-attachments/assets/a0552c8b-93df-4e1d-ad90-8b8abf9492b1"
/>

[Lightning CSS
Playground](https://lightningcss.dev/playground/index.html#%7B%22minify%22%3Afalse%2C%22customMedia%22%3Atrue%2C%22cssModules%22%3Afalse%2C%22analyzeDependencies%22%3Afalse%2C%22targets%22%3A%7B%22chrome%22%3A6225920%7D%2C%22include%22%3A0%2C%22exclude%22%3A0%2C%22source%22%3A%22%40layer%20theme%2C%20base%2C%20components%2C%20utilities%3B%5Cn%5Cn%40supports%20(((-webkit-hyphens%3A%20none))%20and%20(not%20(margin-trim%3A%20inline)))%20or%20((-moz-orient%3A%20inline)%20and%20(not%20(color%3A%20rgb(from%20red%20r%20g%20b))))%20%7B%5Cn%20%20%40layer%20base%20%7B%5Cn%20%20%20%20*%2C%20%3Abefore%2C%20%3Aafter%2C%20%3A%3Abackdrop%20%7B%5Cn%20%20%20%20%20%20--tw-font-weight%3A%20initial%3B%5Cn%20%20%20%20%7D%5Cn%20%20%7D%5Cn%7D%5Cn%5Cn%40layer%20theme%20%7B%5Cn%20%20%3Aroot%2C%20%3Ahost%20%7B%5Cn%20%20%20%20--font-sans%3A%20ui-sans-serif%2C%20system-ui%2C%20sans-serif%2C%20%5C%22Apple%20Color%20Emoji%5C%22%2C%20%5C%22Segoe%20UI%20Emoji%5C%22%2C%20%5C%22Segoe%20UI%20Symbol%5C%22%2C%20%5C%22Noto%20Color%20Emoji%5C%22%3B%5Cn%20%20%20%20--font-mono%3A%20ui-monospace%2C%20SFMono-Regular%2C%20Menlo%2C%20Monaco%2C%20Consolas%2C%20%5C%22Liberation%20Mono%5C%22%2C%20%5C%22Courier%20New%5C%22%2C%20monospace%3B%5Cn%20%20%20%5Cn%20%20%7D%5Cn%7D%5Cn%5Cn%40layer%20base%20%7B%5Cn%20%20*%2C%20%3Aafter%2C%20%3Abefore%2C%20%3A%3Abackdrop%20%7B%5Cn%20%20%20%20box-sizing%3A%20border-box%3B%5Cn%20%20%20%20border%3A%200%20solid%3B%5Cn%20%20%20%20margin%3A%200%3B%5Cn%20%20%20%20padding%3A%200%3B%5Cn%20%20%7D%5Cn%7D%5Cn%5Cn%40layer%20utilities%20%7B%5Cn%20%20.text-2xl%20%7B%5Cn%20%20%20%20font-size%3A%20var(--text-2xl)%3B%5Cn%20%20%20%20line-height%3A%20var(--tw-leading%2C%20var(--text-2xl--line-height))%3B%5Cn%20%20%7D%5Cn%7D%5Cn%5Cn%40property%20--tw-font-weight%20%7B%5Cn%20%20syntax%3A%20%5C%22*%5C%22%3B%5Cn%20%20inherits%3A%20false%5Cn%7D%22%2C%22visitorEnabled%22%3Afalse%2C%22visitor%22%3A%22%7B%5Cn%20%20Color(color)%20%7B%5Cn%20%20%20%20if%20(color.type%20%3D%3D%3D%20'rgb')%20%7B%5Cn%20%20%20%20%20%20color.g%20%3D%200%3B%5Cn%20%20%20%20%20%20return%20color%3B%5Cn%20%20%20%20%7D%5Cn%20%20%7D%5Cn%7D%22%2C%22unusedSymbols%22%3A%5B%5D%2C%22version%22%3A%22local%22%7D)

Fixes: #17494
2025-04-02 11:05:35 +02:00
Philipp Spiess
b069d7a5a1
Disable padding in @source inline(…) brace expansion (#17491)
The padding code we had was incorrect as it would always pad on the
largest string representation. So for an input like this:


```
@source inline("z-{10..100..10}");
```

It would create the following candidates: 

- `z-010`
- `z-020`
- `z-030`
- `z-040`
- `z-050`
- `z-060`
- `z-070`
- `z-060`
- `z-070`
- `z-100`

Instead of fixing the padding logic we realized that Tailwind utilities
don't need padding at all so this PR removes this feature

## Test plan

- Added the following to the Vite playground: `@source
inline("z-{10..100..10}");`
- Ensure it works: 

![image](https://github.com/user-attachments/assets/f4714729-4ef7-4678-a531-70b471e75e6e)
2025-04-01 22:55:03 +02:00
Philipp Spiess
8f631d0d8a
Prepare 4.1.0 release (#17483)
---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-04-01 18:05:18 +02:00
Robin Malfait
b94720aef3
Refactor codemod structure (#17484)
This PR is an internal refactor of the codemods package structure that
will make a few follow-up PRs easier.

Essentially what happens is:


1. Moved `./src/template/` into `src/codemods/template/`
2. Moved `./src/codemods` into `./src/codemods/css` (because the CSS
related codemods already)
3. Moved the migration files for the JS config, PostCSS config and
Prettier config into `./src/codemods/config/`.
4. Made filenames with actual migrations consistent by prefixing them
with `migrate-`.
5. Made sure that all the migration functions also use `migrate…`

When looking at this PR, go commit by commit, it will be easier. In a
lot of cases, it's just moving files around but those commits also come
with changes to the code just to update the imports.

[ci-all]
2025-04-01 17:12:22 +02:00
Jordan Pittman
9374647b86
Add drop-shadow-* color support (#17434)
This PR adds support for two things:

- New `drop-shadow-{color}` utilities which can be used to control the
filder drop shadow color
- New `drop-shadow-*/{alpha}` utilities which can be used to control the
intensity of the drop shadow

Note that drop-shadow-* utilities without a modifier use variables from
your theme.

If you opt into using drop shadow colors or drop-shadow "intensity"
utilities we will inline these values into your CSS when using a drop
shadow color or drop shadow intensity utility and as such can't be
changed by changing the theme variables on a per-element/tree basis.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-04-01 17:11:49 +02:00
Philipp Spiess
55d7a65cfc
Add self-baseline-last (#17476)
This PR also adds a new `self-baseline-last` utility similar to
`items-baseline-last` added previously. We've also discussed adding a
`content-baseline-last` utility but since the browser support for this
[is very
limited](https://caniuse.com/mdn-css_properties_align-content_flex_context_last_baseline)
and we weren't able to get this to do anything even in the supported
browsers, we decided against it.

## Test plan

Updated the utility tests.
2025-04-01 07:55:41 -04:00
Philipp Spiess
f590be43b8 Add changelog for #17435 2025-04-01 13:34:22 +02:00
Philipp Spiess
156afc6d67
Improve compatibility with Safari 15 (#17435)
This PR improves the compatibility with Tailwind CSS v4 with unsupported
browsers with the goal to greatly improve compatibility with Safari 15.

To make this work, this PR makes the following changes to all code

- Change `oklab(…)` default theme values to use a percentage in the
first place (so instead of `--color-red-500: oklch(0.637 0.237 25.331);`
we now define it as `--color-red-500: oklch(63.7% 0.237 25.331);` since
this syntax has much broader support on Safari).
- Polyfill `@property` with a `@supports` query targeting older versions
of Safari and Firefox *
- Create fallbacks for the `color-mix(…)` function that use _inlined
color values from your theme_ so that they can be computed a compile
time by `lightningcss`. These fallbacks will convert to srgb to increase
compatibility.
- Create fallbacks for the _relative color_ feature used in the new
shadow utilities and using `color-mix(…)` in case _relative color_ is
applied on `currentcolor` (due to limited browser support)
- Create fallbacks for gradient interpolation methods (e.g. to support
`bg-linear-to-r/oklab`)
- Polyfill `@media` queries range syntax.

## A simplified example

Given this example CSS input:

```css
@import 'tailwindcss';
@source inline('from-cyan-500/50 bg-linear-45');
```

Here's the updated output CSS including the newly added polyfills and
updated `oklab` values:

```css
.bg-linear-45 {
  --tw-gradient-position: 45deg;
  background-image: linear-gradient(var(--tw-gradient-stops));
}

@supports (background-image: linear-gradient(in lab, red, red)) {
  .bg-linear-45 {
    --tw-gradient-position: 45deg in oklab;
  }
}

.from-cyan-500\\/50 {
  --tw-gradient-from: oklab(71.5% -.11682 -.08247 / .5);
  --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}

@supports (color: color-mix(in lab, red, red)) {
  .from-cyan-500\\/50 {
    --tw-gradient-from: color-mix(in oklab, var(--color-cyan-500) 50%, transparent);
  }
}

:root, :host {
  --color-cyan-500: oklch(71.5% .143 215.221);
}

@supports (((-webkit-hyphens: none)) and (not (margin-trim: 1lh))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
  @layer base {
    *, :before, :after, ::backdrop {
      --tw-gradient-position: initial;
      --tw-gradient-from: #0000;
      --tw-gradient-via: #0000;
      --tw-gradient-to: #0000;
      --tw-gradient-stops: initial;
      --tw-gradient-via-stops: initial;
      --tw-gradient-from-position: 0%;
      --tw-gradient-via-position: 50%;
      --tw-gradient-to-position: 100%;
    }
  }
}

@property --tw-gradient-position {
  syntax: "*";
  inherits: false
}

@property --tw-gradient-from {
  syntax: "<color>";
  inherits: false;
  initial-value: #0000;
}

@property --tw-gradient-via {
  syntax: "<color>";
  inherits: false;
  initial-value: #0000;
}

@property --tw-gradient-to {
  syntax: "<color>";
  inherits: false;
  initial-value: #0000;
}

@property --tw-gradient-stops {
  syntax: "*";
  inherits: false
}

@property --tw-gradient-via-stops {
  syntax: "*";
  inherits: false
}

@property --tw-gradient-from-position {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 0%;
}

@property --tw-gradient-via-position {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 50%;
}

@property --tw-gradient-to-position {
  syntax: "<length-percentage>";
  inherits: false;
  initial-value: 100%;
}
```

## \* A note on `@property` polyfills and CSS modules

On Next.js, CSS module files are required to be _pure_, meaning that all
selectors must either be scoped to a class or an ID. Fortunatnyl for us,
this does not apply to `@property` rules which we've been using before
to initialize CSS variables.

However, since we're now bringing back the `@property` polyfills, that
would cause unexpected rules to be exported from the CSS file as this:

```css
@reference "tailwindcss";

.skew {
  @apply skew-7;
}
```

Would turn to the following file:

```css
.skew {
  /* … */
}
@supports (/*…*/) {
  @layer base {
    *, :before, :after, ::backdrop {
      --tw-gradient-position: initial;
    }
  }
}
@property /* … */ 
```

Notice that this adds a `*` selector which is not considered pure.

Unfortunately there is no way for us to silence this warning or work
around it, as the dependency causing this errors
([`postcss-modules-local-by-default`](https://github.com/css-modules/postcss-modules-local-by-default))
is bundled into Next.js. To work around crashes, these polyfills will
not apply to CSS modules processed by the PostCSS extension for now.

## Testing on tailwindcss.com

To see the changes in effect, take a look at this screencast that
compares tailwindcss.com on iOS 15.5 with a version that has the patches
of this PR applied:

https://github.com/user-attachments/assets/1279d6f5-3c63-4f30-839c-198a789f4292

## Test plan

- Tested on tailwindcss.com via a preview build:
https://tailwindcss-com-git-legacy-browsers-tailwindlabs.vercel.app/
- Updated tests
- Ensure we also test on Chrome 111, Safari 16.4, Firefox 128 to
make sure we have no regressions. Also tested on Safari 16.4, 15.5, 18.0
2025-04-01 13:33:22 +02:00
depfu[bot]
80017ebabe
Update all of nextjs 15.2.3 → 15.2.4 (patch) (#17471) 2025-04-01 10:48:54 +02:00
Robin Malfait
d54e23d5a1
Ensure webpack executable is found in CI (#17470)
This PR will fix CI on the main branch where `webpack` could not be
found on macOS (but it worked on Linux).

Going to run [ci-all] to verify the changes.
2025-03-31 19:21:51 +02:00
Robin Malfait
53801091a0
Watch CSS module files for changes (#17467)
This PR is a follow-up PR for:
https://github.com/tailwindlabs/tailwindcss/pull/17433

In the other PR we allow scanning CSS files for extracting usages of CSS
variables. This is important for `.module.css` files that reference
these variables but aren't in the same big AST of the main CSS file.

This PR also makes sure to watch for changes in those registered CSS
files and re-extract the variables when they change.

This PR took a bit longer than expected because I was trying to make
sure that writing to `./dist/out.css` works without infinite-looping
(e.g.: we had issues with this in Tailwind CSS v3 with webpack).

But I couldn't reproduce the issue at all. I did had some code that
tried to detect if the CSS file contained license headers and skip in
(because then it's very likely an output CSS file) but even without it
the tests were fine.

I setup integration tests with `@tailwindcss/cli` itself, and with tools
that use webpack. Added a test for Next.js, and a dedicated webpack test
as well.

Even without tests, locally, I couldn't reproduce an infinite loop due
to changes in an output CSS file...

Eventually dropped the code that tries to detect output CSS files.

One thing to keep in mind is that if you change any of your "main" CSS
files, then we will trigger a full rebuild anyway, so this change is
only required for unrelated CSS files (like CSS module files) that use
CSS variables.

## Test plan

1. Added integration tests for the CLI and Next.js
2. Added new dedicated test for webpack
2025-03-31 18:44:06 +02:00
Philipp Spiess
c7ba564f92
Fix slow unit test (#17465)
This PR reworks a unit test that created a file in the project root and
then proceeded by scanning everything in the git root for candidates.
The issue specifically is that with the `.debug/` folder, our project
root can grow quite a bit which makes this test slower the more you work
on other tests...

To fix this we now simply create a tmp folder with only that one test
file. 🚀
2025-03-31 15:26:01 +02:00
Philipp Spiess
eec1bf2b84
Fix --theme(…) function when legacy JS plugins are used (#17458)
Closes #17346

This PR fixes an issue that caused the `--theme(…)` function to behave
differently after a legacy JS plugin or config was configured. The issue
was that the compatibility layer would patch the theme value resolver to
always inline the value. This, however, is only expected to happen if
the path does not look like a CSS variable in which case this legacy
code path should not be run.

To fix this, I'm now keeping a reference to the regular theme resolution
function and call into it if the path starts with `--`.

## Test plan

- Tested with the repro in #17346 by adding pnpm overrides and confirmed
that this fixes the issue
- Added a unit test to the `--theme(…)` resolution tests
2025-03-31 13:06:28 +02:00
Jordan Pittman
c32b6082a1
Add object-{top,bottom}-{left,right} utilities (#17437)
These match the new `mask-*` and updated `bg-*` utilities.

This is the same as #17378 but for `object-position`.

| Deprecated utility    | New utility           |
| --------------------- | --------------------- |
| `object-left-top`     | `object-top-left`     |
| `object-right-top`    | `object-top-right`    |
| `object-left-bottom`  | `object-bottom-left`  |
| `object-right-bottom` | `object-bottom-right` |
2025-03-28 15:58:17 -04:00
Jordan Pittman
f77226652a
Add new mask-image utilities for Tailwind CSS v4.1 (#17134) 2025-03-28 13:24:40 -04:00
Jordan Pittman
1a68b99368
Add support for “intensity” modifiers on box and text shadows (#17398)
This PR adds support for utilities like:
- `text-shadow-lg/25`

It uses relative color syntax to replace the alpha value of the shadow
from your theme.

When combined with a colors:
- `text-shadow-lg/25 text-shadow-red-500`
- `text-shadow-lg/25 text-shadow-red-500/75`

The alpha values are **multiplied** resulting in a shadow with the color
specified in `--color-red-500` and alpha values of 25% and 18.75%
respectively.
2025-03-28 13:16:00 -04:00
Philipp Spiess
5e255deb0e
Add text-shadow-* utilities (#17389)
This PR adds new `text-shadow-*` utilities and default values courtesy
of @danhollick's.

Usage is similar to the normal `shadow-*` utilities, for example:

```html
<h1 class="text-center text-7xl tracking-tight text-white text-shadow-xl">
  Some fancy <br />
  headline
</h1>
```

Since this PR also adds first-class support for the `--text-shadow`
theme namespace, it also means it resolves #17047.

## Test plan

- Copied @danhollick's [demo
playground](https://play.tailwindcss.com/lbA3Y6VY8u) to the `vite` setup
and ran a production build to ensure it looks correct:
https://tailwind-text-shadows-preview-2kdnjb32b.vercel.app/

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-03-28 13:06:56 -04:00
Robin Malfait
2af7c57983
Extract used CSS variables from .css files (#17433)
This PR fixes an issue where CSS variables could be used in CSS modules,
but where never emitted in your final CSS.

Some backstory, when Tailwind CSS v4 came out, we _always_ emitted all
CSS variables whether they were used or not.

Later, we added an optimization where we only emit the CSS variables
that were actually used. The definition of "used" in this case is:

1. Used in your CSS  file(s) — (we check the final CSS AST for this)
2. Used _somewhere_ in any of your source files (e.g.: a JavaScript file
accessing a variable)

The issue this PR tries to solve is with the very first point. If you
are using CSS modules, then every CSS file is processed separately. This
is not a choice Tailwind CSS made, but how other build tooling works
(like Vite for example).

To prevent emitting all of Tailwind's Preflight reset and all utilities
per CSS file, you can use the `@reference` directive instead of
repeating `@import "tailwindcss";`. This is explained here:
https://tailwindcss.com/docs/compatibility#explicit-context-sharing

But now we are just _referencing_ them, not emitting them. And since the
CSS module is not connected in any way to the main `index.css` file that
contains the `@import "tailwindcss";` directive, we don't even see the
CSS variables while processing the `index.css` file. (or wherever your
main CSS file is)

This is where point 2 from above comes in. This is a situation where we
rely on the extractor to find the used CSS variables so we can
internally mark them as used.

To finally get to the point of this PR, the extractor only scans
`.html`, `.js`, ... files but not `.css` files. So all the CSS variables
used inside of CSS modules will not be generated.

This PR changes that behavior to also scan `.css` files. But _only_ for
CSS variables (not any other type of class candidate). This is
important, otherwise all your custom `@utility foo {}` definitions would
always mark `foo` as a used class and include it in the CSS which is not
always the case.

On top extracting CSS variables, we will also make sure that the CSS
variables we find are in usage positions (e.g.: `var(--color-red-500)`)
and not in definition positions (e.g.: `--color-red-500: #ff0000;`).
This is important because we only want to emit the variables that are
actually used in the final CSS output.

One future improvement not implemented here, is that technically we will
also extract CSS variables that might not be used if defined in a
`@utility`.

```css
@utility never-used {
  color: var(--color-red-500); /* --color-red-500 will be emitted, even if it might not be used */
}
```

Fixes: #16904
Fixes: #17429

# Test plan

1. Added a test where CSS variables are defined in `.css` files (and
ignored)
2. Added a test where CSS variables are used in `.css` files (and
included)

Testing on the reproduction defined in #16904, the `.module.css` file
contains a reference to `var(--color-hot-pink)`, but generating a build
shows that the variable definition is not available:

<img width="1630" alt="image"
src="https://github.com/user-attachments/assets/a0d5c37e-6813-4cd5-a677-6c356b5a73d4"
/>

When you run the build again with the changes from this PR, then we _do_
see the definition of the `--color-hot-pink` in the root CSS file:
<img width="2876" alt="image"
src="https://github.com/user-attachments/assets/beab7c11-a31b-4ea4-8235-4849a8e92859"
/>
2025-03-28 17:53:15 +01:00
Jordan Pittman
3412a9623d
Add bg-{position,size}-* utilities (#17432)
This PR adds `bg-{position,size}-*` utilities that support arbitrary
values. This exist as the new preferred version of something like this:

```
bg-[120px_120px]
```

Is it size or position (hint: it's the 2nd one).

To override it you'd have to provide a data type:
```
bg-[size:120px_120px]
```


Now you can be more explicit and have better intellisense by writing
these:
```
bg-position-[120px_120px]
bg-size-[120px_120px]
```
2025-03-28 16:40:57 +00:00
Jordan Pittman
ab868c6098
Improve arbitrary value validation when parsing candidates (#17361)
Fixes #17357

This affects the CDN and Play

Now candidates like these no longer parse and emit CSS:
- `[--foo:1rem;--bar:2rem]`
- `[&{color:red}]:flex`
- `data-[a]{color:red}foo[a]:flex`
2025-03-28 12:38:57 -04:00
Jordan Pittman
1b6230f54f
Rework scripting variants (#17431)
This replaces the `scripting`, `scripting-none`, and `scripting-initial`
variants with one `noscript` variant that matches the name of the HTML
`<noscript>` element. The previous `scripting` can then be represented
as `not-noscript` e.g. `not-noscript:flex`.
2025-03-28 12:28:20 -04:00
Philipp Spiess
9cceeaa87f Fix PR number on change log 2025-03-28 17:14:50 +01:00
Robin Malfait
601f369374
Extract special @("@")md:… syntax in Razor files (#17427)
This PR fixes an extraction issue in Razor files where `@@md:bg-red-500`
can't always be extracted properly. We already convert `@@md:bg-red-500`
to ` @md:bg-red-500` but in certain situations Razor will emit the
double `@@` to the DOM.

A workaround in Razor land would be to write `@("@")md:bg-red-500`
instead. See: https://github.com/dotnet/aspnetcore/issues/38595 But then
we don't extract the `@md:bg-red-500` properly anymore.

This is where this PR comes in, essentially we will pre process the
Razor contents and apply the following replacement internally:

```diff
- @("@")md:bg-red-500
+      @md:bg-red-500
```
Notice that the `)` looks like it's replaced with `@`. This will have a
small side effect later when we get to the testing part.

But this way we properly see the `@md:bg-red-500` class during class
extraction.

> [!WARNING]
> There is technically a bug here because of the replacement with `@`,
because if you now run the `npx @tailwindcss/upgrade@latest` tool, then
we would replace `)md:bg-red-500` if changes are required, not the
`@("@")md:bg-red-500` part. We can try to fix that in this PR but it
seems unlikely that we will actually run into this issue realistically
speaking. I think fixing the actual extraction here is much more
important than the upgrade tooling that could fail _if_ we ever have to
migrate `@md:…` to something else.

Fixes: #17424

## Test plan

1. Added a test to verify the fix
2. Existing tests pass
3. Verified this in the extractor tool, but it looks a tiny bit funky.

Typically we remove characters by replacing it with a space ` `. But
this time, we replace it with some spaces _and_ an `@` character that
didn't exist at that specific position. If you look at the diff above,
you will notice that `)` was replaced with `@`.

That's why in the extractor tool it is highlighted that it could extract
it, but it's just funny looking because it highlights `)md:bg-red-500`

<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/57e6a3ac-bfd5-4cad-a1ce-0039b4d7d9b5"
/>
2025-03-28 14:37:54 +01:00
Robin Malfait
c3ae9d1567
Improve changelog for @source not (#17375)
We didn't add a changelog for the PR
(https://github.com/tailwindlabs/tailwindcss/pull/17255) and there are a
few things we need to talk about. So opened this PR to get everything in
and once we're happy we can merge it in one go instead of changing on
`main` directly.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-28 11:32:10 +00:00
depfu[bot]
ec2f3bb1ad
Update @types/react 19.0.10 → 19.0.12 (patch) (#17377)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react (19.0.10 → 19.0.12) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-03-28 10:56:17 +01:00
Jordan Pittman
3e53e25669
Add bg-{top,bottom}-{left,right} utilities (#17378)
We’re using this naming for `mask-*` and `mask-radial-at-*` position
utilities and we want to be consistent. The old ones for
`background-position` will stay but are deprecated

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-03-27 12:43:02 +01:00
Robin Malfait
eecb6f7476
A bit of cleanup (#17394)
Was working on another issue and noticed that I wanted these but they
aren't related to any of the issues. So opening a separate PR to do some
internal cleanup.
2025-03-26 12:08:53 -04:00
Robin Malfait
224ce0b19b
update changelog 2025-03-26 15:47:28 +01:00
Robin Malfait
b0c48c3de9
Fix symlink issues when resolving @source directives (#17391)
This PR fixes some issues related to symlinks when using them in the
`@source` directive.

Fixes: #16765
Fixes: #16038

## Test plan

1. Added tests to prove this works
   - Added a recursive symlink test as well to make sure we don't hang
2. Existing tests still pass


[ci-all]
2025-03-26 08:11:55 -04:00
Philipp Spiess
18365fff6c Prepare v4.0.17 release 2025-03-26 12:15:19 +01:00
Jordan Pittman
3dcd615b02
Fix Ruby files causing the CLI to hang (#17383)
Fixes #17379

The preprocessor we added to detect embedded languages uses a back
reference and given a long enough file with certain byte / character
patterns it'll cause what appears to be an indefinite hang (might just
be catastrophically exponential backtracking but not sure)

This replaces the one regex w/ back references with two, anchored,
multi-line regexes

Now we search for all the starting & ending delimiters in the file. We
then loop over all the starting delimiters, find the paired ending one,
and preprocess the content inside

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-26 11:00:10 +01:00
Robin Malfait
c45616ff91
update changelog 2025-03-25 15:55:46 +01:00
Robin Malfait
1ef97759e3
Add @source not support (#17255)
This PR adds a new source detection feature: `@source not "…"`. It can
be used to exclude files specifically from your source configuration
without having to think about creating a rule that matches all but the
requested file:

```css
@import "tailwindcss";
@source not "../src/my-tailwind-js-plugin.js";
```

While working on this feature, we noticed that there are multiple places
with different heuristics we used to scan the file system. These are:

- Auto source detection (so the default configuration or an `@source
"./my-dir"`)
- Custom sources ( e.g. `@source "./**/*.bin"` — these contain file
extensions)
- The code to detect updates on the file system

Because of the different heuristics, we were able to construct failing
cases (e.g. when you create a new file into `my-dir` that would be
thrown out by auto-source detection, it'd would actually be scanned). We
were also leaving a lot of performance on the table as the file system
is traversed multiple times for certain problems.

To resolve these issues, we're now unifying all of these systems into
one `ignore` crate walker setup. We also implemented features like
auto-source-detection and the `not` flag as additional _gitignore_ rules
only, avoid the need for a lot of custom code needed to make decisions.

High level, this is what happens after the now:

- We collect all non-negative `@source` rules into a list of _roots_
(that is the source directory for this rule) and optional _globs_ (that
is the actual rules for files in this file). For custom sources (i.e
with a custom `glob`), we add an allowlist rule to the gitignore setup,
so that we can be sure these files are always included.
- For every negative `@source` rule, we create respective ignore rules.
- Furthermore we have a custom filter that ensures files are only read
if they have been changed since the last time they were read.

So, consider the following setup:

```css
/* packages/web/src/index.css */
@import "tailwindcss";
@source "../../lib/ui/**/*.bin";
@source not "../../lib/ui/expensive.bin";
```

This creates a git ignore file that (simplified) looks like this:

```gitignore
# Auto-source rules
*.{exe,node,bin,…}
*.{css,scss,sass,…}
{node_modules,git}/

# Custom sources can overwrite auto-source rules
!lib/ui/**/*.bin

# Negative rules
lib/ui/expensive.bin
```

We then use this information _on top of your existing `.gitignore`
setup_ to resolve files (i.e so if your `.gitignore` contains rules e.g.
`dist/` this line is going to be added _before_ any of the rules lined
out in the example above. This allows negative rules to allow-list your
`.gitignore` rules.

To implement this, we're rely on the `ignore` crate but we had to make
various changes, very specific, to it so we decided to fork the crate.
All changes are prefixed with a `// CHANGED:` block but here are the
most-important ones:

- We added a way to add custom ignore rules that _extend_ (rather than
overwrite) your existing `.gitignore` rules
- We updated the order in which files are resolved and made it so that
more-specific files can allow-list more generic ignore rules.
- We resolved various issues related to adding more than one base path
to the traversal and ensured it works consistent for Linux, macOS, and
Windows.

## Behavioral changes

1. Any custom glob defined via `@source` now wins over your `.gitignore`
file and the auto-content rules.
   - Resolves #16920
3. The `node_modules` and `.git` folders as well as the `.gitignore`
file are now ignored by default (but can be overridden by an explicit
`@source` rule).
   - Resolves #17318
   - Resolves #15882
4. Source paths into ignored-by-default folders (like `node_modules`)
now also win over your `.gitignore` configuration and auto-content
rules.
    -  Resolves #16669
5. Introduced `@source not "…"` to negate any previous rules.
   - Resolves #17058
6. Negative `content` rules in your legacy JavaScript configuration
(e.g. `content: ['!./src']`) now work with v4.
   - Resolves #15943 
7. The order of `@source` definitions matter now, because you can
technically include or negate previous rules. This is similar to your
`.gitingore` file.
9. Rebuilds in watch mode now take the `@source` configuration into
account
   - Resolves #15684

## Combining with other features

Note that the `not` flag is also already compatible with [`@source
inline(…)`](https://github.com/tailwindlabs/tailwindcss/pull/17147)
added in an earlier commit:

```css
@import "tailwindcss";
@source not inline("container");
```

## Test plan

- We added a bunch of oxide unit tests to ensure that the right files
are scanned
- We updated the existing integration tests with new `@source not "…"`
specific examples and updated the existing tests to match the subtle
behavior changes
- We also added a new special tag `[ci-all]` that, when added to the
description of a PR, causes the PR to run unit and integration tests on
all operating systems.

[ci-all]

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-25 15:54:41 +01:00
Robin Malfait
f44cfffaa3
use correct heading level for v4.0.15 2025-03-25 15:52:51 +01:00
Robin Malfait
acaccf7fcb
fix typo 2025-03-25 15:52:41 +01:00
Robin Malfait
1c50b5c16c
Prepare v4.0.16 release (#17372)
Prepare the 4.0.16 release.

~~Also added a commit to mark the `--value('…')` and `--modifier('…')`
with literals strings as an experimental feature (aka not shipped in
this PR). But we can revert that commit if we still want to ship it in
4.0.16 instead of 4.1.~~

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-25 15:30:32 +01:00
Jordan Pittman
bd501e8511
Add ::-webkit-details-marker pseudo to marker variant (#17362)
See #17360

This PR updates the `marker` variant to also target the marker present
in `<summary>` elements in WebKit browsers. Chromium uses `::marker` and
is therefore already covered.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-25 13:22:00 +01:00
Philipp Spiess
e8715d081e
Extract keyframe name when followed by comma (#17352)
Fixes #17332

This PR ensures that keyframes are emitted even when they are referenced
following a comma, e.g.:

```css
@theme {
  --animate-test: 500ms both fade-in, 1000ms linear 500ms spin infinite;
                                 /* ^ */

  @keyframes fade-in {
    from {
      opacity: 0%;
    }
    to {
      opacity: 100%;
    }
  }
}
```

## Test plan

Added a unit test to capture the issue from #17332
2025-03-24 12:25:41 +01:00
Rudi Visser
baa016a1c9
Add Input & Output check to CLI (#17311)
Throw an error if the input and output file for the CLI are identical.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-03-24 12:00:36 +01:00
Robin Malfait
711b9cd0db
Pre process Slim templates embedded in Ruby files (#17336)
This PR fixes an issue where embedded Slim templates inside of Ruby
files are not pre processed because we pre process based on a file
extension.

This PR also handles embedded SLIM templates using the following syntax:

```rb
slim_template <<~SLIM
  .flex
    .flex-1
      h1 Hello World
    .flex-1
      p This is a test
SLIM
```

~~As far as I can tell, this is only a Slim template thing and not a
Haml template thing but I could be wrong here. See:
https://viewcomponent.org/guide/templates.html#interpolations~~

The ViewComponent package handles anything that looks like
`{lang}_template`, so the lang here will be used as the pre processing
language for now.

Fixes: #17334

# Test plan

1. Added test for this
2. Existing tests pass
3. Made sure that the snippet from the issue works as expected:

Added an example where we have a `slim_template` and a `svelte_template`
to prove that it embeds based on the language. I also added a
`html_template` with Svelte syntax to really make sure that that
_doesn't_ work.

<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/35564a32-9c46-4b51-bb1f-e02f4ffe8b01"
/>
2025-03-24 11:58:37 +01:00
Philipp Spiess
fac8f7df62
Vite: Emit build dependencies on partial rebuilds (#17347)
Closes #17339

This PR fixes an issue that caused changes to `@import`-ed CSS files to
no longer rebuild the stylesheet after a change was made to a template
file.

The change in the template file causes a fast-path in the Vite plugin
now after changes in 4.0.8: _partial rebuilds_. For that branch we do
not need to re-evaluate your input CSS since we know only the candidate
list changed. However, we still need to emit all build dependencies as
via `addWatchFile(…)`, otherwise Vite will not correctly register
updates for these dependencies anymore.

## Test plan

- Updated the kitchen-sink Vite update tests to ensure that an
`@import`-ed CSS file can be updated even after a partial rebuild.
- Ensure this works in our Vite playground
2025-03-24 11:54:55 +01:00
Philipp Spiess
42f68bb359
Fix changelog (#17348)
This change was added in #17320 which was not released yet, see commit
order on `main`:
https://github.com/tailwindlabs/tailwindcss/commits/main/
2025-03-24 11:39:22 +01:00
depfu[bot]
1c481b81c7
Update all of nextjs 15.2.1 → 15.2.3 (patch) (#17337) 2025-03-24 11:26:05 +01:00
Robin Malfait
5426baf358
Fix class extraction followed by ( in Pug (#17320)
This PR fixes an issue where a class shorthand in Pug followed by a `(`
is not properly extracted.

```html
<template lang="pug">
.text-sky-600.bg-neutral-900(title="A tooltip") This div has an HTML attribute.
</template>
```

The `text-sky-600` is extracted, but the `bg-neutral-900` is not.

Fixes: #17313

# Test plan

1. Added test to cover this case
2. Existing tests pass (after a few small adjustments due to _more_
extracted candidates, but definitely not _less_)
3. Verified against the original issue (top is before, bottom is this
PR)
<img width="1307" alt="image"
src="https://github.com/user-attachments/assets/68a0529f-63ad-477d-a342-e3f91c5a1690"
/>

We had this exact same bug in Slim
(https://github.com/tailwindlabs/tailwindcss/pull/17278). Since Pug,
Slim and Haml are the only pre processors we have right now with this
dot-separated class notation I also double checked the Haml
pre-processor if this is an issue or not (and it's already covered
there).

<img width="1263" alt="image"
src="https://github.com/user-attachments/assets/c658168b-d124-46c9-9ec0-9697151a57bf"
/>
2025-03-21 15:43:37 +01:00
Jordan Pittman
91c0d56d0f Revert "Temporarily revert changes to `@utility"
This reverts commit 1aab04cebf.
2025-03-20 17:00:23 -04:00
Jordan Pittman
498b06f2d9 Fix the other two workflows 2025-03-20 16:38:19 -04:00
Jordan Pittman
1aab04cebf Temporarily revert changes to `@utility
The github release workflow is currently broken on the 4.0.15 tag for whatever reason and we need to get the release out
2025-03-20 16:21:48 -04:00
Jordan Pittman
508746b996 Remove fetch-tags from release workflow
The current checkout action can encounter a bug where, if this workflow was triggered by a tag, this breaks
2025-03-20 15:55:21 -04:00
Robin Malfait
250c843341
Add suggestions when --spacing(--value(integer, number)) is used (#17308)
This PR adds suggestions to CSS based functional utilities when the
`--spacing(…)` function is used.

Given this CSS:

```css
@import "tailwindcss";

@theme {
  --spacing: 0.25rem;
  --spacing-custom: 123px;
}

@utility with-custom-spacing-* {
  size: --value(--spacing);
}

@utility with-integer-spacing-* {
  size: --spacing(--value(integer));
}

@utility with-number-spacing-* {
  size: --spacing(--value(number));
}
```
    
And this HTML:
    
```html
<div class="with-custom-spacing-custom"></div>
<div class="with-custom-spacing-0"></div>
<div class="with-custom-spacing-0.5"></div>
<div class="with-custom-spacing-1"></div>
<div class="with-custom-spacing-1.5"></div>

<div class="with-integer-spacing-custom"></div>
<div class="with-integer-spacing-0"></div>
<div class="with-integer-spacing-0.5"></div>
<div class="with-integer-spacing-1"></div>
<div class="with-integer-spacing-1.5"></div>

<div class="with-number-spacing-custom"></div>
<div class="with-number-spacing-0"></div>
<div class="with-number-spacing-0.5"></div>
<div class="with-number-spacing-1"></div>
<div class="with-number-spacing-1.5"></div>
```
    
Play: https://play.tailwindcss.com/tYDaSNiNtS


Then you will see the following suggestions:

```json
[
  "with-custom-spacing-custom",

  "with-integer-spacing-0",
  "with-integer-spacing-1",
  "with-integer-spacing-2",
  "with-integer-spacing-3",
  "with-integer-spacing-4",
  "with-integer-spacing-5",
  "with-integer-spacing-6",
  "with-integer-spacing-7",
  "with-integer-spacing-8",
  "with-integer-spacing-9",
  "with-integer-spacing-10",
  "with-integer-spacing-11",
  "with-integer-spacing-12",
  "with-integer-spacing-14",
  "with-integer-spacing-16",
  "with-integer-spacing-20",
  "with-integer-spacing-24",
  "with-integer-spacing-28",
  "with-integer-spacing-32",
  "with-integer-spacing-36",
  "with-integer-spacing-40",
  "with-integer-spacing-44",
  "with-integer-spacing-48",
  "with-integer-spacing-52",
  "with-integer-spacing-56",
  "with-integer-spacing-60",
  "with-integer-spacing-64",
  "with-integer-spacing-72",
  "with-integer-spacing-80",
  "with-integer-spacing-96",

  "with-number-spacing-0",
  "with-number-spacing-0.5",
  "with-number-spacing-1",
  "with-number-spacing-1.5",
  "with-number-spacing-2",
  "with-number-spacing-2.5",
  "with-number-spacing-3",
  "with-number-spacing-3.5",
  "with-number-spacing-4",
  "with-number-spacing-5",
  "with-number-spacing-6",
  "with-number-spacing-7",
  "with-number-spacing-8",
  "with-number-spacing-9",
  "with-number-spacing-10",
  "with-number-spacing-11",
  "with-number-spacing-12",
  "with-number-spacing-14",
  "with-number-spacing-16",
  "with-number-spacing-20",
  "with-number-spacing-24",
  "with-number-spacing-28",
  "with-number-spacing-32",
  "with-number-spacing-36",
  "with-number-spacing-40",
  "with-number-spacing-44",
  "with-number-spacing-48",
  "with-number-spacing-52",
  "with-number-spacing-56",
  "with-number-spacing-60",
  "with-number-spacing-64",
  "with-number-spacing-72",
  "with-number-spacing-80",
  "with-number-spacing-96"
]
```

This is because `--spacing(--value(number))` will include all default
spacing scale suggestions we use. And `--pacing(--value(integer))` will
include the same list but without the floating point numbers.

Follow up PR for: https://github.com/tailwindlabs/tailwindcss/pull/17304
2025-03-20 19:06:07 +00:00
Robin Malfait
a3316f2ef4
Add support for literal values in --value('…') and --modifier('…') (#17304)
This PR adds support for literal values inside the `--value('…')` and
`--modifier('…')` functions. This allows you to safelist some known
values you want to use:

E.g.:

```css
@utility tab-* {
  tab-size: --value('revert', 'initial');
}
```

This allows you to use `tab-revert` and `tab-initial` for example.
2025-03-20 19:02:02 +00:00
Philipp Spiess
4c57d9f734
Prepare v4.0.15 release (#17302)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create an issue to first
discuss any significant new features. This includes things like adding
new utilities, creating new at-rules, or adding new component examples
to the documentation.


https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md

-->
2025-03-20 17:51:30 +01:00
Philipp Spiess
40a76e3380
Revert "Don't use color-mix(…) on currentColor (#17247)" and work around Preflight crash (#17306)
Closes #17194.

This reverts commit d6d913ec39.

The initial fix does breaks older versions of Chrome (where text won't
render with a color for the placeholder at all anymore) and the usage of
the _relative colors_ features also means it'll require a much newer
version of Safari/Firefox/Chrome to work correctly. The implementation
was also wrong as it always set alpha to the specific percent instead of
applying it additively (note that this can be fixed with `calc(alpha *
opacity)` though).

Instead we decided to fix this by adding a `@supports` query to
Preflight that only targets browsers that aren't affected by the crash.
We currently use the following workaround:

```css
/*
  Set the default placeholder color to a semi-transparent version of the current text color in browsers that do not
  crash when using `color-mix(…)` with `currentColor`. (https://github.com/tailwindlabs/tailwindcss/issues/17194)
*/

@supports (not (-webkit-appearance: -apple-pay-button)) /* Not Safari */ or
  (contain-intrinsic-size: 1px) /* Safari 17+ */ {
  ::placeholder {
    color: color-mix(in oklab, currentColor 50%, transparent);
  }
}
```

## Test plan

When testing the `color-mix(currentColor)` vs `oklab(from currentColor
…)` we created the following support matrix. I'm extending it with _our
fix_ which is the fix ended up using:

| Browser | Version | `color-mix(… currentColor …)` | `oklab(from
currentColor …)` | `@supports { color-mix(…) }` |
| ------- | ------- | ----------------------------- |
---------------------------- | ------- |
| Chrome | 111 | ❌ | ❌ | ❌ |
| Chrome | 116 | ✅ | ❌ | ✅ |
| Chrome | 131+ | ✅ | ✅ | ✅ |
| Safari | 16.4 | 💥 | ❌ | ❌ |
| Safari | 16.6+ | ✅ | ❌ | ❌ |
| Safari | 18+ | ✅ | ✅ | ✅ |
| Firefox | 128 | ✅ | ❌ | ✅ |
| Firefox | 133 | ✅ | ✅ | ✅ |

Note that on Safari 16, this change makes it so that the browser does
not crash yet it still won't work either. That's because now the browser
will fall back to the default placeholder color instead. We used the
following play to test the fix: https://play.tailwindcss.com/RF1RYbZLKY
2025-03-20 17:43:32 +01:00
Philipp Spiess
3f313b4eb2
Ensure that the CSS file rebuilds if a new CSS variable is used from templates (#17301)
Fixes #17288

This PR fixes an issue where changes in template files that _only mark a
new CSS variable as used_ was not causing the CSS file to rebuilds.

This also fixes a flaky integration test that was caused by a missing
`await` line on the final assertion (causing it to sometimes run the
assertion in time while the test runner was still running).

## Test plan

Turns out we already had an integration test for this but it wasn't
correctly running due to the missing await.
2025-03-20 12:52:02 +01:00
Philipp Spiess
ca7b10e7d3
Make --theme(…) return CSS variables (#17036)
Closes #16945

This PR changes the `--theme(…)` function now return CSS `var(…)`
definitions unless used in places where `var(…)` is not valid CSS (e.g.
in `@media (width >= theme(--breakpoint-md))`):

```css
/* input */
@theme {
  --color-red: red;
}
.red {
  color: --theme(--color-red);
}

/* output */
:root, :host {
  --color-red: red;
}
.red {
  color: var(--color-red);
}
```

Furthermore, this adds an `--theme(… inline)` option to the `--theme(…)`
function to force the resolution to be inline, e.g.:

```css
/* input */
@theme {
  --color-red: red;
}
.red {
  color: --theme(--color-red inline);
}

/* output */
.red {
  color: red;
}
```

This PR also changes preflight and the default theme to use this new
`--theme(…)` function to ensure variables are prefixed correctly.

## Test plan

- Added unit tests and a test that pulls in the whole preflight under a
prefix theme.
2025-03-20 12:51:22 +01:00
Philipp Spiess
a1acaeeee0
Export PluginUtils from tailwindcss/plugin (#17299)
Closes #17293

We used to export this in v3 so might as well bring it back for the
`tailwindcss/plugin` export list.
2025-03-20 12:19:45 +01:00
Philipp Spiess
503bad4e75
Use bun-baseline for all x64 builds (#17267)
Closes #17259

This PR now also updates the MacOS x64 build to use `bun-baseline`,
meaning all x64 builds now use baseline for the improved hardware
compatibility.

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-03-20 12:16:04 +01:00
Robin Malfait
f369e22172
Fix class extraction followed by ( in Slim (#17278)
This PR fixes an issue where using the class shorthand in Slim
templates, followed by an `(` results in the last class being ignored.

E.g.:

```slim
body.border-t-4.p-8(class="#{body_classes}" data-hotwire-native="#{hotwire_native_app?}" data-controller="update-time-zone")
```

This is because we will eventually extract `p-8` but it's followed by an
invalid boundary character `(`.

To solve this, we make sure to replace the `(` with a space. We already
do a similar thing when the classes are followed by an `[`.

One caveat, we _can_ have `(` in our classes, like `bg-(--my-color)`.
But in my testing this is not something that can be used in the
shorthand version.

E.g.:
```slim
div.bg-(--my-color)
```

Compiles to:
```html
<div --my-color="" class="bg-"></div>
```

So I didn't add any special handling for this. Even when trying to
escape the `(`, `-` and `)` characters, it still doesn't work. E.g.:

```slim
div.bg-\(--my-color\)
```

Compiles to:
```html
<div class="bg-">\(--my-color\)</div>
```

# Test plan

1. Added test for the issue
2. Existing tests pass
3. Verified via the extractor tool:

| Before | After |
| --- | --- |
| <img width="958" alt="image"
src="https://github.com/user-attachments/assets/f72c420e-5429-424f-a01d-12f724062bf2"
/> | <img width="958" alt="image"
src="https://github.com/user-attachments/assets/b0cc8f2f-97a8-4fca-8813-3bb1da8d99a8"
/> |

---

Fixes: #17277
2025-03-19 09:50:30 -04:00
Robin Malfait
d698c10836
Ensure multiple --value(…) or --modifier(…) calls don't delete subsequent declarations (#17273)
This PR fixes a bug in the handling of `@utility`. Essentially if you
had a declaration where you used a `--modifier(…)` _and_ a `--value(…)`
and both caused the declaration to be removed, the declaration after the
current one would be removed as well.

This happened because 2 reasons:

1. Once we removed the declaration when we handled `--modifier(…)`, we
didn't stop the walk and kept going.
2. The `replaceWith(…)` code allows you to call the function multiple
times but immediately mutates the AST. This means that if you call it
multiple times that you are potentially removing / updating nodes
followed by the current one.

E.g.:

```css
@utility mask-r-* {
  --mask-right: linear-gradient(to left, transparent, black --value(percentage));
  --mask-right: linear-gradient(
    to left,
    transparent calc(var(--spacing) * --modifier(integer)),
    black calc(var(--spacing) * --value(integer))
  );
  mask-image: var(--mask-linear), var(--mask-radial), var(--mask-conic);
}
```

If this is used as `mask-r-10%`, then the first definition of
`--mask-right` is kept, but the second definition of `--mask-right` is
deleted because both `--modifier(integer)` and `--value(integer)` do not
result in a valid value.

However, the `mask-image` declaration was also removed because the
`replaceWith(…)` function was called twice. Once for
`--modifier(integer)` and once for `--value(integer)`.

# Test plan

1. Added a test to cover this case.
2. Existing tests pass.
3. Added a hard error if we call `replaceWith(…)` multiple times.
2025-03-19 13:25:53 +00:00
Devon Govett
cec7f0557b
Fix segmentation fault when loading @tailwindcss/oxide in a Worker thread (#17276)
When Tailwind is loaded in a Node Worker thread, it currently causes a
segmentation fault on Linux when the thread exits. This is due to a
longstanding issue in Rust that affects all native modules:
https://github.com/rust-lang/rust/issues/91979. I reported this years
ago but unfortunately it is still not fixed, and seems to have gotten
worse in Rust 1.83.0 and later. Looks like Tailwind recently updated
Rust versions and this issue started appearing when run in tools like
Parcel that use worker threads.

The workaround is to prevent the native module from ever being unloaded.
One way to do that is to always load the native module in the main
thread in addition to workers, but this is hard to enforce.
@Brooooooklyn found another method, which is to use a linker option for
this. I tested this on an Ubuntu system and verified it fixed the issue.
You can test with the following script.

```js
// test.js
const {Worker} = require('worker_threads');
new Worker('./worker.js');

// worker.js
require('@tailwindcss/oxide');
```

Without this change, a segmentation fault will occur.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-03-18 16:28:20 -04:00
Martijn Cuppens
1564bf092b
Remove redundant line-height from body (#15212)
It was added in #2729 to override line heights set on the body by
modern-normalize. However, it appears that modern-normalize never
included any line-height definitions—only a font-family rule was
present.

Ref:
https://github.com/sindresorhus/modern-normalize/blob/v1.1.0/modern-normalize.css

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-18 12:23:49 +01:00
Robin Malfait
d7c81164da
Pre process <template lang="…"> in Vue files (#17252)
This PR fixes an issue where `<template lang="…">…</template>` in Vue
files should be handled as-if it's the language specified in the `lang`
attribute.

To do this, we added a new Vue pre processor and run the content through
the same pre processor logic as we do for other languages.

Fixes: #17211

# Test plan

1. Added a test to verify this works
2. Existing tests still work

Visually verified against the reproduction in the issue:

| Before | After |
| --- | --- |
| <img width="1273" alt="image"
src="https://github.com/user-attachments/assets/d1accdeb-97cf-48ef-83fb-978832b3e599"
/> | <img width="1273" alt="image"
src="https://github.com/user-attachments/assets/ab7ec19c-b6c4-43be-8845-096ff4e58808"
/> |

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-03-17 13:54:37 +00:00
Benoît Rouleau
ebfde316e1
Add safe alignment utilities (#14607)
Fixes #7929 and #12916

This PR adds [safe alignment
utilities](https://www.stefanjudis.com/today-i-learned/safe-unsafe-alignment-in-css-flexbox/)
to Tailwind 4. I opted to include them for every justify/align
content/items/self property, but only for the `end` and `center` values.
I know that it doesn't make sense for `start` (as the point of safe
alignment is to fall back to `start` when it overflows), but I'm not
sure about `space-between`, `space-around`, or other values. I certainly
never encountered a situation where I needed `safe` with those.
2025-03-17 14:09:15 +01:00
Bendegúz Hajnal
1d2d50ee66
Add items-baseline-last utility (#13888)
This PR adds the `items-first-baseline` and `items-last-baseline`
utility classes that can control
[`baseline-position`](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items#formal_syntax)
for the [align-items
property](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items#baseline).

Browser support:
- MDN:
https://developer.mozilla.org/en-US/docs/Web/CSS/align-items#browser_compatibility
- Can I use `first baseline`:
https://caniuse.com/?search=align%20items%3A%20first%20baseline
- Can I use `last baseline`:
https://caniuse.com/?search=align%20items%3A%20last%20baseline

The feature were requested multiple times:

- https://github.com/tailwindlabs/tailwindcss/discussions/13518
- https://github.com/tailwindlabs/tailwindcss/discussions/12406
- https://github.com/tailwindlabs/tailwindcss/discussions/11623
- https://github.com/tailwindlabs/tailwindcss/discussions/4855

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-17 13:51:13 +01:00
Philipp Spiess
d6d913ec39
Don't use color-mix(…) on currentColor (#17247)
Closes #17194

This PR works around a crash when rendering opacity on `currentColor`
(as used by the placeholder styles in preflight) on Safari 16.4 and
Safari 16.5. Unfortunately it seems that the [`color-mix(…)` function is
not compatible with `currentColor` for these versions of
Safari](https://stackoverflow.com/questions/76436497/the-color-mix-property-involving-currentcolor-causes-safari-to-crash).
We tried a few different ways to work around this without success:

- Using an `@supports` media query to target these Safari versions and
overwriting the placeholder still makes these browsers crash.
- Changing the way we apply opacity to `currentColor` in core doesn't
seem to work for non-placeholder values:
https://github.com/tailwindlabs/tailwindcss/issues/17194#issuecomment-2728949181
However, a wrong opacity is still better than a complete browser crash.

The work-around of using the `oklab(…)` function does seem to work for
`::placeholder` styles in preflight though according to our testing so
this PR applies this change to preflight.

## Test plan

- See https://play.tailwindcss.com/WSsSTLHu8h?file=css 
- Tested on Chrome/Safari 16.4/Safari 18.3/Firefox

<img width="564" alt="Screenshot 2025-03-17 at 11 32 47"
src="https://github.com/user-attachments/assets/cfd0db71-f39a-4bc0-bade-cea70afe50ae"
/>
2025-03-17 13:23:02 +01:00
Robin Malfait
448949318e
Fix inferring data type of border-[…] with multiple values (#17248)
This PR fixes an issue where arbitrary values such as
`border-[12px_4px]` were incorrectly producing `border-color` instead of
`border-width` values.

To solve it, I extended the `<line-width>` check to make sure that every
part of the value is a valid `<line-width>`.

In order for a `line-width` to be valid, every part should be one of:

1. A keyword: `thin`, `medium`, `thick`
2. A length: `12px`
3. A number: `0`

Fixes: #17221

# Test plan

1. Added test to verify this works
2. All existing tests pass
2025-03-17 12:35:55 +01:00
depfu[bot]
1a88518d80
Update autoprefixer 10.4.20 → 10.4.21 (patch) (#17229) 2025-03-17 09:59:18 +00:00
depfu[bot]
6b2b26287c
Update eslint 9.20.1 → 9.22.0 (minor) (#17207) 2025-03-17 10:55:59 +01:00
depfu[bot]
48957c5411
Update @playwright/test 1.50.1 → 1.51.0 (minor) (#17184) 2025-03-14 17:39:35 +01:00
Jordan Pittman
50562a9c94
Fix -bg-conic-* utilities (#17174)
The negative versions were missing the `deg` in `<number>deg` so they
didn’t work whoops
2025-03-13 10:13:56 -04:00
Robin Malfait
3c5903c1ee
Prepare v4.0.14 release (#17173) 2025-03-13 12:36:34 +01:00
Robin Malfait
221855b195
Ensure candidate extraction works as expected in Clojure/ClojureScript (#17087)
This PR adds a Clojure/ClojureScript pre processor to make sure that
candidate extraction works as expected.


| Before | After |
| --- | --- |
| <img width="908" alt="image"
src="https://github.com/user-attachments/assets/98aba8b6-0c44-47c6-b87c-ecf955e5e007"
/> | <img width="908" alt="image"
src="https://github.com/user-attachments/assets/7a5ec3eb-1630-4b60-80bd-c07bc2381d3b"
/> |

You can see that the classes preceded by `:` are not properly extracted
in the before case, but they are in the after case. We do extract a few
more cases now like `:class` and `:className` itself, but at least we
also retrieve all the `flex-*` classes.

We could also always ignore `:class` and `:className` literals:
<img width="908" alt="image"
src="https://github.com/user-attachments/assets/f5a67cae-25d6-4811-b777-f72fdb5ef450"
/>
2025-03-13 11:31:27 +01:00
Philipp Spiess
74ccde4672
Upgrade lightningcss to 1.29.2 in Standalone (#17169)
Closes #17162
Closes #17163
Closes #17164
Closes #17165
Closes #17166
Closes #17167

Noticed that there was a second place pulling in the lightningcss
version numbers that wasn't covered by the previous upgrade PR.
Thankfully the APIs of the node bindings are still compatible.

To avoid this mistake in the future I also exported these sub-packages
as a workspace dependency so all the version strings appear right next
to each other.
2025-03-13 11:18:06 +01:00
depfu[bot]
26f91d2a43
Update turbo 2.4.2 → 2.4.4 (patch) (#17157) 2025-03-13 10:50:06 +01:00
Robin Malfait
cedd54fecf
Fix variants with <digit>.</digit> are extracted correctly (#17153)
This PR fixes an issue where if you use a number with a decimal in a
variant then it wasn't picked up correctly.

E.g.:
```
<div class="2xl:flex 1.5xl:flex"></div>
            ^^^^^^^^                        Picked up
                     ^^^^^^^^^^             Not picket up
```

This PR fixes that behavior by applying the same rules for utilities
where a `.` is valid if it is surrounded by numbers.

# Test plan

1. Added test to ensure this is picked up
2. Existing tests pass
3. Ran the extractor on a real example with the following results:

| Before | After |
| --- | --- |
| <img width="821" alt="image"
src="https://github.com/user-attachments/assets/a77ed5e4-6848-4fe3-8cbf-cf61ff8db41d"
/> | <img width="821" alt="image"
src="https://github.com/user-attachments/assets/61aca66a-e38d-4b61-bf86-e6286a89a3d9"
/> |

They are crossed out just because it's not a default value we know in
the system, but you can see that the `1.` part is also extracted now.

Fixes: #17148
2025-03-12 21:04:11 +00:00
Philipp Spiess
215f4f348b
Add @source inline(…) (#17147)
This PR adds a new experimental feature that can be used to force-inline
utilities based on an input string. The idea is that all utilities
matching the source string will be included in your CSS:

```css
/* input.css */
@source inline('underline');

/* output.css */
.underline {
   text-decoration: underline;
}
```

Additionally, the source input is brace-expanded, meaning you can use
one line to inline a whole namespace easily:
```css
/* input.css */
@source inline('{hover:,}bg-red-{50,{100..900..100},950}');

/* output.css */
.bg-red-50 {
  background-color: var(--color-red-50);
}
.bg-red-100 {
  background-color: var(--color-red-100);
}
.bg-red-200 {
  background-color: var(--color-red-200);
}
.bg-red-300 {
  background-color: var(--color-red-300);
}
.bg-red-400 {
  background-color: var(--color-red-400);
}
.bg-red-500 {
  background-color: var(--color-red-500);
}
.bg-red-600 {
  background-color: var(--color-red-600);
}
.bg-red-700 {
  background-color: var(--color-red-700);
}
.bg-red-800 {
  background-color: var(--color-red-800);
}
.bg-red-900 {
  background-color: var(--color-red-900);
}
.bg-red-950 {
  background-color: var(--color-red-950);
}
@media (hover: hover) {
  .hover\\:bg-red-50:hover {
    background-color: var(--color-red-50);
  }
  .hover\\:bg-red-100:hover {
    background-color: var(--color-red-100);
  }
  .hover\\:bg-red-200:hover {
    background-color: var(--color-red-200);
  }
  .hover\\:bg-red-300:hover {
    background-color: var(--color-red-300);
  }
  .hover\\:bg-red-400:hover {
    background-color: var(--color-red-400);
  }
  .hover\\:bg-red-500:hover {
    background-color: var(--color-red-500);
  }
  .hover\\:bg-red-600:hover {
    background-color: var(--color-red-600);
  }
  .hover\\:bg-red-700:hover {
    background-color: var(--color-red-700);
  }
  .hover\\:bg-red-800:hover {
    background-color: var(--color-red-800);
  }
  .hover\\:bg-red-900:hover {
    background-color: var(--color-red-900);
  }
  .hover\\:bg-red-950:hover {
    background-color: var(--color-red-950);
  }
}

```

This feature is also compatible with the `not` keyword that we're about
to add to `@source "…"` in a follow-up PR. This can be used to set up an
ignore list purely in CSS. The following code snippet, for example, will
ensure that the `.container` utility is never created:

```css
@theme {
  --breakpoint-sm: 40rem;
  --breakpoint-md: 48rem;
  --breakpoint-lg: 64rem;
  --breakpoint-xl: 80rem;
  --breakpoint-2xl: 96rem;
}
@source not inline("container");
@tailwind utilities;
```

## Test plan

- See added unit tests
- The new brace expansion library was also benchmarked against the
popular `braces` library:
  ```
  clk: ~3.96 GHz
  cpu: Apple M4 Max
  runtime: bun 1.1.34 (arm64-darwin)

benchmark avg (min … max) p75 / p99 (min … top 1%)
-------------------------------------------
-------------------------------
braces 31.05 ms/iter 32.35 ms █ █
(28.14 ms … 36.35 ms) 35.14 ms ██ █
( 0.00 b … 116.45 mb) 18.71 mb ██████▁▁▁██▁█▁██▁█▁▁█

./brace-expansion 19.34 ms/iter 21.69 ms █
(12.53 ms … 26.63 ms) 25.53 ms ▅ ▅ █ █
( 0.00 b … 114.13 mb) 11.86 mb █▁▅▁██▁▅█▅█▅▁█▅█▅▅▁▅█

┌ ┐
╷┌────┬─┐ ╷
braces ├┤ │ ├─────┤
╵└────┴─┘ ╵
                              ╷        ┌────┬───┐       ╷
            ./brace-expansion ├────────┤    │   ├───────┤
                              ╵        └────┴───┘       ╵
└ ┘
12.53 ms 23.84 ms 35.14 ms
  ```

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-03-12 13:55:33 +00:00
Robin Malfait
ca408d0612
Do not extract candidates containing JS string interpolation pattern ${ (#17142)
This PR fixes an issue where often people run into issues where they try
to use string interpolation and it doesn't work. Even worse, it could
result in crashes because we will actually generate CSS. This fix only
filters out candidates with a pattern like `${`. If this occurs in a
string position it is fine.

Another solution would be to add a pre processor for JS/TS (and all
thousand file extension combinations) but the problem is that you can
also write JS in HTML files so we would have to pre process HTML as well
which would not be ideal.

# Test plan

1. Added tests to prove this works in arbitrary values, arbitrary
variables in both utilities and variants.
2. Existing tests pass.
3. Some screenshots with before / after situations:

Given this input:
```ts
let color = '#0088cc';
let opacity = 0.8;
let name = 'variable-name';
let classes = [
  // Arbitrary Properties
  `[color:${color}]`
  `[${property}:value]`,
  `[--img:url('https://example.com?q=${name}')]`, // WONT WORK BUT VALID CSS

  // Arbitrary Values
  `bg-[${color}]`,

  // Arbitrary Variables
  `bg-(--my-${color})`,
  `bg-(--my-color,${color})`,

  // Arbitrary Modifier
  `bg-red-500/[${opacity}]`,
  `bg-red-500/(--my-${name})`,
  `bg-red-500/(--my-opacity,${opacity})`,

  // Arbitrary Variant
  `data-[state=${name}]:flex`,
  `supports-(--my-${name}):flex`,
  `[@media(width>=${value})]:flex`,
];
```

This is the result:

| Before | After |
| --- | --- |
| <img width="908" alt="image"
src="https://github.com/user-attachments/assets/c64d1b16-d39d-48a6-a098-bc4477cb4b0a"
/> | <img width="908" alt="image"
src="https://github.com/user-attachments/assets/d71aaf62-5e13-4174-82bb-690eb81aaeaf"
/> |

Fixes: #17054
Fixes: #15853
2025-03-12 12:09:13 +01:00
Robin Malfait
4455048c0b
Prepare release 4.0.13 (#17132) 2025-03-11 17:58:53 +01:00
Robin Malfait
37062928f9
Improve candidate extraction when candidates contain . characters (#17113)
This PR fixes an issue where some classes weren't properly extracted due
to some incorrect assumptions in the pre processors.

Templating languages such as Haml, Slim and Pug allow you to write
classes in a shorter way that are not properly contained inside of
strings. E.g.:
```slim
p.flex.px-2
```

These candidates are not properly extracted because there are no
bounding characters like quotes. To solve this, we pre-process it and
replace `.` with ` ` characters. This results in something like:
```
p flex px-2
```

However, this has some challenges on its own. Candidates like `px-2.5`
cannot be written in this shorthand form, instead they need to be in
strings. Now we _cannot_ replace the `.` because otherwise we would
change `px-2.5` to `px-2 5` which is wrong.

The next problem is that we need to know when they are in a "string".
This has another set of problems because these templating languages
allow you to write normal text that will eventually be the contents of
the HTML tags.

```haml
.text-red-500.text-3xl
  | This text can't should be red
                 ^ Wait, is this the start of a string now???
```

In this example, if we consider the `'` the start of a string, when it's
clearly not, how would we know it's for _sure_ not a string?

This ended up as a bit of a rabbit hole, but we came up with another
approach entirely if we think about the original problem we want to
solve which is when do we change `.` to ` ` characters.

One of the rules in our current extractor is that a `.` has to be
between 2 numbers. Which works great in a scenario like: `px-2.5`.
However, if you look at Haml or Slim syntax, this is also allowed:

```slim
p.bg-red-500.2xl:flex
           ^^^ Uh oh...
```

In this scenario, a `.` is surrounded by numbers so we shouldn't replace
it with a space. But as you can see, we clearly do... so we need another
heuristic in this case.

Luckily, one of the rules in Tailwind CSS is that a utility cannot start
with a number, but a variant _can_. This means that if we see a scenario
like `<digit>.<digit>` then we can just check if the value after the `.`
is a valid variant or not.

In this case it is a valid variant so we _do_ want to replace the `.`
with a ` ` even though we do have the `<digit>.<digit>` format.

🥴

# Test plan

1. Added additional tests.
2. Existing tests still pass

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-11 17:53:16 +01:00
Robin Malfait
22746e6a6b
drop mise.toml 2025-03-11 17:49:30 +01:00
Philipp Spiess
9d7f25316e
Don't extract links as arbitrary properties (#17129)
Closes #17128

This PR prevents extraction of links inside square brackets as valid
candidate:

```
[https://example/]
```

We do this by throwing out arbitrary properties when the value starts
with a slash (`/`).

## Test plan

- Added unit test

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-03-11 16:40:24 +00:00
Philipp Spiess
785cadeb21
Upgrade lightningcss (#17043)
Part of #15897
2025-03-11 17:39:30 +01:00
Robin Malfait
9ddeb091d7
Add JSON pre-processor (#17125)
This PR adds a small JSON pre processor to improve parsing JSON files.

Due to the sheer amount of potential `[` and `]` brackets, it could be
that parsing JSON files are way slower than they need to be.

We saw this while debugging this issue:
https://github.com/tailwindlabs/tailwindcss/issues/17092


# Test plan

1. Added test to verify the pre processing works
2. Existing tests still pass
2025-03-11 12:15:48 +00:00
Philipp Spiess
8bc633bb17
Add .node and .wasm to known binary file list (#17123)
Closes #17092

After a lot of spelunking we found one specific reason for the very slow
builds in the repro from #17092: Turns our we are needlessly scanning
the binary `.node` extension for class names 😬. This PR adds `.wasm` and
`.node` to the list of known binary extensions.

## Test plan

 - Check out the repro from `#17092`
 - Delete the `.gitignore` file
 - Observe that builds are very slow (`527.79s`)
 - Add a _pnpm override_ to load local versions of Oxide
 - `pnpm build` now completes in ~50s
2025-03-11 13:05:51 +01:00
depfu[bot]
5e2633bb6d
Update all of nextjs 15.1.7 → 15.2.1 (minor) (#17118) 2025-03-11 12:06:10 +01:00
depfu[bot]
a57cd2dce8
Update postcss 8.5.2 → 8.5.3 (patch) (#17116) 2025-03-11 10:58:22 +01:00
Robin Malfait
de145c5b06
Refactor: use compile time type state pattern (#17083)
This PR implements the state machines using the type state pattern at
compile time (via generic types) instead of a runtime state variable.
There is no runtime check to see what state we are in, instead we
transition to the new state when it's necessary.

This has some nice performance improvements for some of the state
machines, e.g.:

```diff
- ArbitraryVariableMachine: Throughput: 744.92 MB/s
+ ArbitraryVariableMachine: Throughput:   1.21 GB/s
```

We also don't have to store the current state because each machine runs
to completion. It's during execution that we can move to a new state if
necessary.


Unfortunately the diff is a tiny bit annoying to read, but essentially
this is what happened:

### The `enum` is split up in it's individual states as structs:
```rs
enum State {
  A,
  B,
  C,
}
```
Becomes:
```rs
struct A;
struct B;
struct C;
```

### Generics

The current machine will receive a generic `State` that we can default
to the `IdleState`. Then we use `PhantomData` to "use" the type because
the generic type is otherwise not used as a concrete value, it's just a
marker.

```rs
struct MyMachine {}
```
Becomes:
```rs
struct MyMachine<State = Idle> {
  _state: std::marker::PhantomData<State>
}
```

### Split 

Next, the `next` function used to match on the current state, but now
each match arm is moved to a dedicated implementation instead:
```rs
impl Machine for MyMachine {
  fn next(&mut self) -> MachineState {
    match self.state {
      State::A => { /* … */ },
      State::B => { /* … */ },
      State::C => { /* … */ },
    }
  }
}
``` 
Becomes:
```rs
impl Machine for MyMachine<A> {
  fn next(&mut self) -> MachineState {
    /* … */
  }
}
impl Machine for MyMachine<B> {
  fn next(&mut self) -> MachineState {
    /* … */
  }
}
impl Machine for MyMachine<C> {
  fn next(&mut self) -> MachineState {
    /* … */
  }
}
```

It's a bit more verbose, but now each state is implemented in its own
block. This also removes 2 levels of nesting which is a nice benefit.
2025-03-10 14:08:39 -04:00
Robin Malfait
cc3e852791
Treat starting single quote as verbatim text in Slim (#17085)
This PR fixes an issue in Slim templates where a single quote `'` at the
start of the line (excluding white space) is considered a line indicator
for verbatim text. It is not considered a string in this scenario.

So something like this:
```slim
div
  'Foo'
```

Will compile to:
```html
<div>Foo'</div>
```

Fixes: #17081
2025-03-10 11:45:26 +01:00
Robin Malfait
bc5a8c3683
Ensure classes between > and < are properly extracted (#17094)
This PR fixes an issue where candidates inside `>…<` were not always
correctly extracted. This happens in XML-like languages where the
classes are inside of these boundaries.

E.g.:
```html
<!-- Fluid template language -->
<f:variable name="bgStyle">
  <f:switch expression="{data.layout}">
    <f:case value="0">from-blue-900 to-cyan-200</f:case>
    <!--              ^^^^^^^^^^^^^^^^^^^^^^^^^      -->
    <f:case value="1">from-cyan-600 to-teal-200</f:case>
    <f:defaultCase>from-blue-300 to-cyan-100</f:defaultCase>
  </f:switch>
</f:variable>
```


Fixes: https://github.com/tailwindlabs/tailwindcss/issues/17088

# Test plan

1. Added a new test
2. Existing tests pass
2025-03-10 11:28:08 +01:00
Robin Malfait
7005ad7e00
Add Haml pre processor (#17051)
This PR ensures we extract candidates from Haml files.

Fixes: #17050
2025-03-07 22:32:15 +01:00
Robin Malfait
2f28e5fbcb
Prepare v4.0.12 release (#17033)
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-07 12:38:53 +01:00
Robin Malfait
f498e4a97c
Ensure extracting candidates from JS embedded in a PHP string works as expected (#17031)
This PR fixes an issue where candidates are not properly extractor when
they end in `\`. This can happen if you embed a programming language
like JS inside another language like PHP where you need to escape some
strings.

Here is an example of Livewire flux:
```blade
@php
if ($sidebarIsStashable) {
    $attributes = $attributes->merge([
        'x-init' => '$el.classList.add(\'-translate-x-full\'); $el.classList.add(\'transition-transform\')',
        //                                                ^                                            ^
    ]);
}
@endphp

<div x-data {{ $attributes->class('border-r w-64 p-4 min-h-dvh max-h-dvh top-0 fixed left-0') }}>
    {{ $slot }}
</div>
```
Where the `\'` is causing some issues.

Another solution might be to add a custom pre processor for blade files
where we drop the escaped characters, but that felt overkill for now
because some escapes are still valid.

Fixes: #17023

# Test plan

1. Added a test to cover this case.
2. Existing tests still pass
2025-03-07 12:19:01 +01:00
Philipp Spiess
225f3233b6
Enable URL rewriting for PostCSS (#16965)
Fixes #16636 

This PR enables URL rebasing for PostCSS. Furthermore it fixes an issue
where transitive imports rebased against the importer CSS file instead
of the input CSS file. While fixing this we noticed that this is also
broken in Vite right now and that our integration test swallowed that
when testing because it did not import any Tailwind CSS code and thus
was not considered a Tailwind file.

## Test plan

- Added regression integration tests
- Also validated it against the repro of
https://github.com/tailwindlabs/tailwindcss/issues/16962:
  
<img width="1149" alt="Screenshot 2025-03-05 at 16 41 01"
src="https://github.com/user-attachments/assets/85396659-d3d0-48c0-b1c7-6125ff8e73ac"
/>

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-03-07 12:18:10 +01:00
Robin Malfait
d18fed1dca
Add razor/cshtml pre processing (#17027)
This PR fixes an issue in Razor template files where `@sm:flex` doesn't
work and `@@sm:flex` is required.

In Tailwind CSS v3, some people used a custom transform to replace `@@`
with just `@`. But in Tailwind CSS v4 we don't have this.

However, we can add a pre processor for `.cshtml` and `.razor` files.

Fixes: #17022
2025-03-07 12:02:07 +01:00
depfu[bot]
bd1e540776
Update h3 1.15.0 → 1.15.1 (patch) (#17010) 2025-03-07 12:00:44 +01:00
Robin Malfait
d0a97467f4
Improve boundary classification (#17005)
This PR cleans up the boundary character checking by using similar
classification techniques as we used for other classification problems.

For starters, this moves the boundary related items to its own file,
next we setup the classification enum.

Last but not least, we removed `}` as an _after_ boundary character, and
instead handle that situation in the Ruby pre processor where we need
it. This means the `%w{flex}` will still work in Ruby files.

---

This PR is a followup for
https://github.com/tailwindlabs/tailwindcss/pull/17001, the main goal is
to clean up some of the boundary character checking code. The other big
improvement is performance. Changing the boundary character checking to
use a classification instead results in:

Took the best score of 10 runs each:
```diff
- CandidateMachine: Throughput: 311.96 MB/s
+ CandidateMachine: Throughput: 333.52 MB/s
```

So a ~20MB/s improvement.

# Test plan

1. Existing tests should pass. Due to the removal of `}` as an after
boundary character, some tests are updated.
2. Added new tests to ensure the Ruby pre processor still works as
expected.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-03-07 00:00:54 +00:00
Robin Malfait
57e91a671a
Ensure } and { are valid boundary characters (#17001) 2025-03-06 20:40:03 +01:00
Robin Malfait
85c6e04f44
Ensure strings in Pug and Slim templates are handled correctly (#17000)
This PR fixes an issue where strings in the Pug and Slim pre-processor
were handled using the `string_machine`. However, the `string_machine`
is not for strings inside of Tailwind CSS classes which means that
whitespace is invalid.

This means that parts of the code that _are_ inside strings will not be
inside strings and parts of the code that are not inside strings will be
part of a potential string. This is a bit confusing to wrap your head
around, but here is a visual representation of the problem:

```
.join(' ')
        ^  3. start of new string, which means that the `)` _could_ be part of a string if a new `'` occurs later.
       ^   2. whitespace is not allowed, stop string
      ^    1. start of string
```

Fixes: #16998

# Test plan

1. Added new test
2. Existing tests still pass
3. Added a simple test helper to make sure that we can extract the
correct candidates _after_ pre-processing
2025-03-06 17:24:00 +01:00
Philipp Spiess
3d0606b82d
Fix sorting of classes that have undefined declarations (#16995)
Closes #16973

Declaration values of `undefined` are an implementation detail and
skipped when printing the CSS. Thus, these should not count towards the
sort order at a..

## Test plan

- See added regression test
2025-03-06 15:20:12 +01:00
Philipp Spiess
b676da8ace
Prepare v4.0.11 release (#16987) 2025-03-06 11:09:39 +00:00
Philipp Spiess
bff387bf6b
Ensure arbitrary variables with data types are extracted correctly (#16986)
Closes #16983

This PR fixes an issue where the arbitrary variable machine did not
extract data type correctly.

## Test plan

- Added regression test
2025-03-06 12:05:33 +01:00
Robin Malfait
af132fbedb
Fix Slim attributes (#16985)
This PR fixes an issue in Slim templates where the start of attributes
causes some candidates to be missing.


```slim
.text-xl.text-red-600[
  data-foo="bar"
]
  | This line should be red
```

Because of the `[` attached to the `text-red-600`, the `text-red-600`
was not extracted because `[` is not a valid boundary character.

To solve this, we copied the Pug pre processor and created a dedicated
Slim pre processor. Next, we ensure that we replace `[` with ` ` in this
scenario (by also making sure that we don't replace `[` where it's
important).

Additionally, we noticed that `.` was also replaced inside of arbitrary
values such as URLs. This has been fixed for both Pug and Slim.

Fixes: #16975

# Test plan

1. Added failing tests
2. Existing tests still pass
2025-03-06 11:59:14 +01:00
Philipp Spiess
617b7abb81
Oxide: Extract arbitrary container queries (#16984)
Closes #16982

Handle the case of variants looking like this: `@[32rem]:flex`.

## Test plan

Added regression tests

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-03-06 11:55:42 +01:00
Hyunmin Woo (Hanul)
4a0236471e
Ensure classes containing number followed by dash or underscore are extracted correctly (#16980)
Fixes #16978, and also added support for dash.
Classes like `text-theme1-primary` or `text-theme1_primary` should be
treated as valid.

If this approach is not aligned with the project’s direction or there
are any concerns, please feel free to close or edit this PR 😃

<br/>

### As is

Classes conatining number followed by dash or underscore (e.g.
`bg-theme1-primary`, `text-title1_strong`) are ignored, and utility
classes are not generated.

### To be

Classes conatining number followed by dash or underscore (e.g.
`bg-theme1-primary`, `text-title1_strong`) are treated as valid
tailwindcss classes

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-06 11:44:43 +01:00
Robin Malfait
9c59b07fb3
Ensure -- is allowed inside candidates (#16972)
This PR fixes an issue where named utilities that contain double dashes
`--` are not extracted correctly.

Some people use `--` in the middle of the utility to create some form of
namespaced utility.

Given this input:
```js
let x = 'foo--bar'
```

The extracted candidates before this change:
```js
[ "let", "x", "--bar" ]
```

The extracted candidates after this change:
```js
[ "let", "x", "foo--bar", "--bar" ]
```

The reason `--bar` is still extracted in both cases is because of the
CSS variable machine. We could improve its extraction by checking its
boundary characters but that's a different issue.

For now, the important thing is that `foo--bar` was extracted.

# Test plan

1. Added new test
2. Existing tests pass
2025-03-05 22:46:43 +01:00
Philipp Spiess
1638b16fee
Prepare v4.0.10 release (#16963)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-03-05 18:32:15 +01:00
Philipp Spiess
a8b64f3465
Vite: Work around crash when .svg file contains # or ? (#16957)
Closes #16877

This PR works around #16877 by not registering `.svg` files containing a
`#` or `?` as a watch dependency for now.

## Test plan

- Add a file to the Vite playground called `src/c#.svg`
- Observe Vite no longer prints errors
2025-03-05 14:55:07 +01:00
Robin Malfait
4c110014f1
Improve internal DX around byte classification [1] (#16864)
This PR improves the internal DX when working with `u8` classification
into a smaller enum. This is done by implementing a `ClassifyBytes` proc
derive macro. The benefit of this is that the DX is much better and
everything you will see here is done at compile time.

Before:
```rs
#[derive(Debug, Clone, Copy, PartialEq)]
enum Class {
    ValidStart,
    ValidInside,
    OpenBracket,
    OpenParen,
    Slash,
    Other,
}

const CLASS_TABLE: [Class; 256] = {
    let mut table = [Class::Other; 256];

    macro_rules! set {
        ($class:expr, $($byte:expr),+ $(,)?) => {
            $(table[$byte as usize] = $class;)+
        };
    }

    macro_rules! set_range {
        ($class:expr, $start:literal ..= $end:literal) => {
            let mut i = $start;
            while i <= $end {
                table[i as usize] = $class;
                i += 1;
            }
        };
    }

    set_range!(Class::ValidStart, b'a'..=b'z');
    set_range!(Class::ValidStart, b'A'..=b'Z');
    set_range!(Class::ValidStart, b'0'..=b'9');

    set!(Class::OpenBracket, b'[');
    set!(Class::OpenParen, b'(');

    set!(Class::Slash, b'/');

    set!(Class::ValidInside, b'-', b'_', b'.');

    table
};
```

After:
```rs
#[derive(Debug, Clone, Copy, PartialEq, ClassifyBytes)]
enum Class {
    #[bytes_range(b'a'..=b'z', b'A'..=b'Z', b'0'..=b'9')]
    ValidStart,

    #[bytes(b'-', b'_', b'.')]
    ValidInside,

    #[bytes(b'[')]
    OpenBracket,

    #[bytes(b'(')]
    OpenParen,

    #[bytes(b'/')]
    Slash,

    #[fallback]
    Other,
}
```

Before we were generating a `CLASS_TABLE` that we could access directly,
but now it will be part of the `Class`. This means that the usage has to
change:

```diff
- CLASS_TABLE[cursor.curr as usize]
+ Class::TABLE[cursor.curr as usize]
```

This is slightly worse UX, and this is where another change comes in. We
implemented the `From<u8> for #enum_name` trait inside of the
`ClassifyBytes` derive macro. This allows us to use `.into()` on any
`u8` as long as we are comparing it to a `Class` instance. In our
scenario:

```diff
- Class::TABLE[cursor.curr as usize]
+ cursor.curr.into()
```

Usage wise, this looks something like this:
```diff
        while cursor.pos < len {
-           match Class::TABLE[cursor.curr as usize] {
+           match cursor.curr.into() {
-               Class::Escape => match Class::Table[cursor.next as usize] {
+               Class::Escape => match cursor.next.into() {
                    // An escaped whitespace character is not allowed
                    Class::Whitespace => return MachineState::Idle,

                    // An escaped character, skip ahead to the next character
                    _ => cursor.advance(),
                },

                // End of the string
                Class::Quote if cursor.curr == end_char => return self.done(start_pos, cursor),

                // Any kind of whitespace is not allowed
                Class::Whitespace => return MachineState::Idle,

                // Everything else is valid
                _ => {}
            };

            cursor.advance()
        }

        MachineState::Idle
    }
}
```


If you manually look at the `Class::TABLE` in your editor for example,
you can see that it is properly generated at compile time.

Given this input:
```rs
#[derive(Clone, Copy, ClassifyBytes)]
enum Class {
    #[bytes_range(b'a'..=b'z')]
    AlphaLower,

    #[bytes_range(b'A'..=b'Z')]
    AlphaUpper,

    #[bytes(b'@')]
    At,

    #[bytes(b':')]
    Colon,

    #[bytes(b'-')]
    Dash,

    #[bytes(b'.')]
    Dot,

    #[bytes(b'\0')]
    End,

    #[bytes(b'!')]
    Exclamation,

    #[bytes_range(b'0'..=b'9')]
    Number,

    #[bytes(b'[')]
    OpenBracket,

    #[bytes(b']')]
    CloseBracket,

    #[bytes(b'(')]
    OpenParen,

    #[bytes(b'%')]
    Percent,

    #[bytes(b'"', b'\'', b'`')]
    Quote,

    #[bytes(b'/')]
    Slash,

    #[bytes(b'_')]
    Underscore,

    #[bytes(b' ', b'\t', b'\n', b'\r', b'\x0C')]
    Whitespace,

    #[fallback]
    Other,
}
```

This is the result:
<img width="1244" alt="image"
src="https://github.com/user-attachments/assets/6ffd6ad3-0b2f-4381-a24c-593e4c72080e"
/>
2025-03-05 14:00:07 +01:00
depfu[bot]
0b36dd51c3
Update @types/bun 1.2.3 → 1.2.4 (patch) (#16955) 2025-03-05 12:30:46 +00:00
Philipp Spiess
50cce50fa4 FreeBSD: rustup toolchain install 2025-03-05 12:48:24 +01:00
Philipp Spiess
9381de498f Don't use beta version for FreeBSD 2025-03-05 12:25:09 +01:00
Philipp Spiess
1c2ad57f03
Add pointer-* variants (#16946)
Adds new variants under a feature flag:

- `pointer-none`
- `pointer-coarse`
- `pointer-fine`
- `any-pointer-none`
- `any-pointer-coarse`
- `any-pointer-fine`
2025-03-05 11:05:17 +00:00
Philipp Spiess
2eecb4d0b6
Remove max-w-auto and max-h-auto (#16917)
Closes #16916

This PR removes `max-w-auto` and `max-h-auto` due to them not being part
of v3 and not being valid CSS:

- https://developer.mozilla.org/en-US/docs/Web/CSS/max-width#syntax
- https://developer.mozilla.org/en-US/docs/Web/CSS/max-height#syntax

<img width="886" alt="Screenshot 2025-03-03 at 14 00 39"
src="https://github.com/user-attachments/assets/2c7fa17c-975b-4e1f-ae56-99b703d6ca70"
/>

<img width="163" alt="Screenshot 2025-03-03 at 13 54 26"
src="https://github.com/user-attachments/assets/12902a14-5a1e-4d7f-8127-fc2a3833221d"
/>

I decided to keep `max-w-auto` and `max-h-auto` since these values are
valid even though these were not in v3 either:

<img width="886" alt="Screenshot 2025-03-03 at 13 55 11"
src="https://github.com/user-attachments/assets/83bf6543-06a3-429e-b39e-ef3ac3290f0b"
/>
2025-03-05 11:59:33 +01:00
depfu[bot]
fe9ab1e969
Update tsup 8.3.6 → 8.4.0 (minor) (#16949) 2025-03-05 10:59:27 +00:00
depfu[bot]
13acd24830
Update bun 1.2.3 → 1.2.4 (patch) (#16954) 2025-03-05 11:56:50 +01:00
Robin Malfait
b3c25563b7
Improve Oxide candidate extractor [0] (#16306)
This PR adds a new candidate[^candidate] extractor with 2 major goals in
mind:

1. It must be way easier to reason about and maintain.
2. It must have on-par performance or better than the current candidate
extractor.

### Problem

Candidate extraction is a bit of a wild west in Tailwind CSS and it's a
very critical step to make sure that all your classes are picked up
correctly to ensure that your website/app looks good.

One issue we run into is that Tailwind CSS is used in many different
"host" languages and frameworks with their own syntax. It's not only
used in HTML but also in JSX/TSX, Vue, Svelte, Angular, Pug, Rust, PHP,
Rails, Clojure, .NET, … the list goes on and all of these have different
syntaxes. Introducing dedicated parsers for each of these languages
would be a huge maintenance burden because there will be new languages
and frameworks coming up all the time. The best thing we can do is make
assumptions and so far we've done a pretty good job at that.

The only certainty we have is that there is at least _some_ structure to
the possible Tailwind classes used in a file. E.g.: `abc#def` is
definitely not a valid class, `hover:flex` definitely is. In a perfect
world we limit the characters that can be used and defined a formal
grammar that each candidate must follow, but that's not really an option
right now (maybe this is something we can implement in future major
versions).

The current candidate extractor we have has grown organically over time
and required patching things here and there to make it work in various
scenarios (and edge cases due to the different languages Tailwind is
used in).

While there is definitely some structure, we essentially work in 2
phases:

1. Try to extract `0..n` candidates. (This is the hard part)
2. Validate each candidate to make sure they are valid looking classes
(by validating against the few rules we have)

Another reason the current extractor is hard to reason about is that we
need it to be fast and that comes with some trade-offs to readability
and maintainability.

Unfortunately there will always be a lot of false positives, but if we
extract more classes than necessary then that's fine. It's only when we
pass the candidates to the core engine that we will know for sure if
they are valid or not. (we have some ideas to limit the amount of false
positives but that's for another time)

### Solution

Since the introduction of Tailwind CSS v4, we re-worked the internals
quite a bit and we have a dedicated internal AST structure for
candidates. For example, if you take a look at this:

```html
<div class="[@media(pointer:fine)]:data-[state=pending]:hover:text-red-500/(--my-opacity)"></div>
```

<details>

<summary>This will be parsed into the following AST:</summary>

```json
[
  {
    "kind": "functional",
    "root": "text",
    "value": {
      "kind": "named",
      "value": "red-500",
      "fraction": null
    },
    "modifier": {
      "kind": "arbitrary",
      "value": "var(--my-opacity)"
    },
    "variants": [
      {
        "kind": "static",
        "root": "hover"
      },
      {
        "kind": "functional",
        "root": "data",
        "value": {
          "kind": "arbitrary",
          "value": "state=pending"
        },
        "modifier": null
      },
      {
        "kind": "arbitrary",
        "selector": "@media(pointer:fine)",
        "relative": false
      }
    ],
    "important": false,
    "raw": "[@media(pointer:fine)]:data-[state=pending]:hover:text-red-500/(--my-opacity)"
  }
]
```

</details>

We have a lot of information here and we gave these patterns a name
internally. You'll see names like `functional`, `static`, `arbitrary`,
`modifier`, `variant`, `compound`, ...

Some of these patterns will be important for the new candidate extractor
as well:

| Name | Example | Description |
| -------------------------- | ----------------- |
---------------------------------------------------------------------------------------------------
|
| Static utility (named) | `flex` | A simple utility with no inputs
whatsoever |
| Functional utility (named) | `bg-red-500` | A utility `bg` with an
input that is named `red-500` |
| Arbitrary value | `bg-[#0088cc]` | A utility `bg` with an input that
is arbitrary, denoted by `[…]` |
| Arbitrary variable | `bg-(--my-color)` | A utility `bg` with an input
that is arbitrary and has a CSS variable shorthand, denoted by `(--…)` |
| Arbitrary property | `[color:red]` | A utility that sets a property to
a value on the fly |

A similar structure exist for modifiers, where each modifier must start
with `/`:

| Name | Example | Description |
| ------------------ | --------------------------- |
---------------------------------------- |
| Named modifier | bg-red-500`/20` | A named modifier |
| Arbitrary value | bg-red-500`/[20%]` | An arbitrary value, denoted by
`/[…]` |
| Arbitrary variable | bg-red-500`/(--my-opacity)` | An arbitrary
variable, denoted by `/(…)` |

Last but not least, we have variants. They have a very similar pattern
but they _must_ end in a `:`.

| Name | Example | Description |
| ------------------ | --------------------------- |
------------------------------------------------------------------------
|
| Named variant | `hover:` | A named variant |
| Arbitrary value | `data-[state=pending]:` | An arbitrary value,
denoted by `[…]` |
| Arbitrary variable | `supports-(--my-variable):` | An arbitrary
variable, denoted by `(…)` |
| Arbitrary variant | `[@media(pointer:fine)]:` | Similar to arbitrary
properties, this will generate a variant on the fly |

The goal with the new extractor is to encode these separate patterns in
dedicated pieces of code (we called them "machines" because they are
mostly state machine based and because I've been watching Person of
Interest but I digress).

This will allow us to focus on each pattern separately, so if there is a
bug or some new syntax we want to support we can add it to those
machines.

One nice benefit of this is that we can encode the rules and handle
validation as we go. The moment we know that some pattern is invalid, we
can bail out early.

At the time of writing this, there are a bunch of machines:

<details>

<summary>Overview of the machines</summary>

- `ArbitraryPropertyMachine`

  Extracts candidates such as `[color:red]`. Some of the rules are:

  1. There must be a property name
  2. There must be a `:`
  3. There must ba a value

There cannot be any spaces, the brackets are included, if the property
is a CSS variable, it must be a valid CSS variable (uses the
`CssVariableMachine`).

  ```
  [color:red]
  ^^^^^^^^^^^

  [--my-color:red]
  ^^^^^^^^^^^^^^^^
  ```

  Depends on the `StringMachine` and `CssVariableMachine`.

- `ArbitraryValueMachine`

Extracts arbitrary values for utilities and modifiers including the
brackets:

  ```
  bg-[#0088cc]
     ^^^^^^^^^

  bg-red-500/[20%]
             ^^^^^
  ```

  Depends on the `StringMachine`.

- `ArbitraryVariableMachine`

Extracts arbitrary variables including the parentheses. The first
argument must be a valid CSS variable, the other arguments are optional
fallback arguments.

  ```
  (--my-value)
  ^^^^^^^^^^^^

  bg-red-500/(--my-opacity)
             ^^^^^^^^^^^^^^
  ```

  Depends on the `StringMachine` and `CssVariableMachine`.

- `CandidateMachine`

Uses the variant machine and utility machine. It will make sure that 0
or more variants are directly touching and followed by a utility.

  ```
  hover:focus:flex
  ^^^^^^^^^^^^^^^^

  aria-invalid:bg-red-500/(--my-opacity)
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ```

  Depends on the `VariantMachine` and `UtilityMachine`.

- `CssVariableMachine`

Extracts CSS variables, they must start with `--` and must contain at
least one alphanumeric character or, `-`, `_` and can contain any
escaped character (except for whitespace).

  ```
  bg-(--my-color)
      ^^^^^^^^^^

  bg-red-500/(--my-opacity)
              ^^^^^^^^^^^^

  bg-(--my-color)/(--my-opacity)
      ^^^^^^^^^^   ^^^^^^^^^^^^
  ```

- `ModifierMachine`

  Extracts modifiers including the `/`

  - `/[` will delegate to the `ArbitraryValueMachine`
  - `/(` will delegate to the `ArbitraryVariableMachine`

  ```
  bg-red-500/20
            ^^^

  bg-red-500/[20%]
            ^^^^^^

  bg-red-500/(--my-opacity)
            ^^^^^^^^^^^^^^^
  ```

  Depends on the `ArbitraryValueMachine` and `ArbitraryVariableMachine`.

- `NamedUtilityMachine`

Extracts named utilities regardless of whether they are functional or
static.

  ```
  flex
  ^^^^

  px-2.5
  ^^^^^^
  ```

  This includes rules like: A `.` must be surrounded by digits.

  Depends on the `ArbitraryValueMachine` and `ArbitraryVariableMachine`.

- `NamedVariantMachine`

Extracts named variants regardless of whether they are functional or
static. This is very similar to the `NamedUtilityMachine` but with
different rules. We could combine them, but splitting things up makes it
easier to reason about.

  Another rule is that the `:` must be included.

  ```
  hover:flex
  ^^^^^^

  data-[state=pending]:flex
  ^^^^^^^^^^^^^^^^^^^^^

  supports-(--my-variable):flex
  ^^^^^^^^^^^^^^^^^^^^^^^^^
  ```

Depends on the `ArbitraryVariableMachine`, `ArbitraryValueMachine`, and
`ModifierMachine`.

- `StringMachine`

This is a low-level machine that is used by various other machines. The
only job this has is to extract strings that start with double quotes,
single quotes or backticks.

We have this because once you are in a string, we don't have to make
sure that brackets, parens and curlies are properly balanced. We have to
make sure that balancing brackets are properly handled in other
machines.

  ```
  content-["Hello_World!"]
           ^^^^^^^^^^^^^^

  bg-[url("https://example.com")]
          ^^^^^^^^^^^^^^^^^^^^^
  ```

- `UtilityMachine`

Extracts utilities, it will use the lower level `NamedUtilityMachine`,
`ArbitraryPropertyMachine` and `ModifierMachine` to extract the utility.

It will also handle important markers (including the legacy important
marker).

  ```
  flex
  ^^^^

  bg-red-500/20
  ^^^^^^^^^^^^^

  !bg-red-500/20      Legacy important marker
  ^^^^^^^^^^^^^^

  bg-red-500/20!      New important marker
  ^^^^^^^^^^^^^^

  !bg-red-500/20!     Both, but this is considered invalid
  ^^^^^^^^^^^^^^^
  ```

Depends on the `ArbitraryPropertyMachine`, `NamedUtilityMachine`, and
`ModifierMachine`.

- `VariantMachine`

Extracts variants, it will use the lower level `NamedVariantMachine` and
`ArbitraryValueMachine` to extract the variant.

  ```
  hover:focus:flex
  ^^^^^^
        ^^^^^^
  ```

  Depends on the `NamedVariantMachine` and `ArbitraryValueMachine`.

</details>

One important thing to know here is that each machine runs to
completion. They all implement a `Machine` trait that has a
`next(cursor)` method and returns a `MachineState`.

The `MachineState` looks like this:

```rs
enum MachineState {
  Idle,
  Done(Span)
}
```

Where a `Span` is just the location in the input where the candidate was
found.

```rs
struct Span {
  pub start: usize,
  pub end: usize,
}
```

#### Complexities

**Boundary characters:**

When running these machines to completion, they don't typically check
for boundary characters, the wrapping `CandidateMachine` will check for
boundary characters.

A boundary character is where we know that even though the character is
touching the candidate it will not be part of the candidate.

```html
<div class="flex"></div>
<!--       ^    ^    -->
```

The quotes are touching the candidate `flex`, but they will not be part
of the candidate itself, so this is considered a valid candidate.

**What to pick?**

Let's imagine you are parsing this input:

```html
<div class="hover:flex"></div>
```

The `UtilityMachine` will find `hover` and `flex`. The `VariantMachine`
will find `hover:`. This means that at a certain point in the
`CandidateMachine` you will see something like this:

```rs
let variant_machine_state = variant_machine.next(cursor);
//  MachineState::Done(Span { start: 12, end: 17 })        // `hover:`

let utility_machine_state = utility_machine.next(cursor);
//  MachineState::Done(Span { start: 12, end: 16 })        // `hover`
```

They are both done, but which one do we pick? In this scenario we will
always pick the variant because its range will always be 1 character
longer than the utility.

Of course there is an exception to this rule and it has to do with the
fact that Tailwind CSS can be used in different languages and
frameworks. A lot of people use `clsx` for dynamically applying classes
to their React components. E.g.:

```tsx
<div
  class={clsx({
    underline: someCondition(),
  })}
></div>
```

In this scenario, we will see `underline:` as a variant, and `underline`
as a utility. We will pick the utility in this scenario because the next
character is whitespace so this will never be a valid candidate
otherwise (variants and utilities must be touching). Another reason this
is valid, is because there wasn't a variant present prior to this
candidate.

E.g.:

```tsx
<div
  class={clsx({
    hover:underline: someCondition(),
  })}
></div>
```

This will be considered invalid, if you do want this, you should use
quotes.

E.g.:

```tsx
<div
  class={clsx({
    'hover:underline': someCondition(),
  })}
></div>
```

**Overlapping/covered spans:**

Another complexity is that the extracted spans for candidates can and
will overlap. Let's take a look at this C# example:

```csharp
public enum StackSpacing
{
  [CssClass("gap-y-4")]
  Small,

  [CssClass("gap-y-6")]
  Medium,

  [CssClass("gap-y-8")]
  Large
}
```

In this scenario, `[CssClass("gap-y-4")]` starts with a `[` so we have a
few options here:

1. It is an arbitrary property, e.g.: `[color:red]`
2. It is an arbitrary variant, e.g.: `[@media(pointer:fine)]:`

When running the parsers, both the `VariantMachine` and the
`UtilityMachine` will run to completion but end up in a
`MachineState::Idle` state.

- This is because it is not a valid variant because it didn't end with a
`:`.
- It's also not a valid arbitrary property, because it didn't include a
`:` to separate the property from the value.

Looking at the code as a human it's very clear what this is supposed to
be, but not from the individual machines perspective.

Obviously we want to extract the `gap-y-*` classes here.

To solve this problem, we will run over an additional slice of the
input, starting at the position before the machines started parsing
until the position where the machines stopped parsing.

That slice will be this one: `[CssClass("gap-y-6")]` (we already skipped
over the whitespace). Now, for every `[` character we see, will start a
new `CandidateMachine` right after the `[`'s position and run the
machines over that slice. This will now eventually extract the `gap-y-6`
class.

The next question is, what if there was a `:` (e.g.:
`[CssClass("gap-y-6")]:`), then the `VariantMachine` would complete, but
the `UtilityMachine` will not because not exists after it. We will apply
the same idea in this case.

Another issue is if we _do_ have actual overlapping ranges. E.g.: `let
classes = ['[color:red]'];`. This will extract both the `[color:red]`
and `color:red` classes. You have to use your imagination, but the last
one has the exact same structure as `hover:flex` (variant + utility).

In this case we will make sure to drop spans that are covered by other
spans.

The extracted `Span`s will be valid candidates therefore if the outer
most candidate is valid, we can throw away the inner candidate.

```
Position:                       11112222222
                                67890123456
                                ↓↓↓↓↓↓↓↓↓↓↓

Span { start: 17, end: 25 }  //  color:red
Span { start: 16, end: 26 }  // [color:red]
```

#### Exceptions

**JavaScript keys as candidates:**

We already talked about the `clsx` scenario, but there are a few more
exceptions and that has to do with different syntaxes.

**CSS class shorthand in certain templating languages:**

In Pug and Slim, you can have a syntax like this:

```pug
.flex.underline
  div Hello World
```

<details>

<summary>Generated HTML</summary>

```html
<div class="flex underline">
  <div>Hello World</div>
</div>
```

</details>

We have to make sure that in these scenarios the `.` is a valid boundary
character. For this, we introduce a pre-processing step to massage the
input a little bit to improve the extraction of the data. We have to
make sure we don't make the input smaller or longer otherwise the
positions might be off.

In this scenario, we could simply replace the `.` with a space. But of
course, there are scenarios in these languages where it's not safe to do
that.

If you want to use `px-2.5` with this syntax, then you'd write:

```pug
.flex.px-2.5
  div Hello World
```

But that's invalid because that technically means `flex`, `px-2`, and
`5` as classes.

You can use this syntax to get around that:

```pug
div(class="px-2.5")
  div Hello World
```

<details>

<summary>Generated HTML</summary>

```html
<div class="px-2.5">
  <div>Hello World</div>
</div>
```

</details>

Which means that we can't simply replace `.` with a space, but have to
parse the input. Luckily we only care about strings (and we have a
`StringMachine` for that) and ignore replacing `.` inside of strings.

**Ruby's weird string syntax:**

```ruby
%w[flex underline]
```

This is valid syntax and is shorthand for:

```ruby
["flex", "underline"]
```

Luckily this problem is solved by the running the sub-machines after
each `[` character.

### Performance

**Testing:**

Each machine has a `test_…_performance` test (that is ignored by
default) that allows you to test the throughput of that machine. If you
want to run them, you can use the following command:

```sh
cargo test test_variant_machine_performance --release -- --ignored
```

This will run the test in release mode and allows you to run the ignored
test.

> [!CAUTION]
> This test **_will_** fail, but it will print some output. E.g.:

```
tailwindcss_oxide::extractor::variant_machine::VariantMachine: Throughput: 737.75 MB/s over 0.02s
tailwindcss_oxide::extractor::variant_machine::VariantMachine:   Duration: 500ns
```

**Readability:**

One thing to note when looking at the code is that it's not always
written in the cleanest way but we had to make some sacrifices for
performance reasons.

The `input` is of type `&[u8]`, so we are already dealing with bytes.
Luckily, Rust has some nice ergonomics to easily write `b'['` instead of
`0x5b`.

A concrete example where we had to sacrifice readability is the state
machines where we check the `previous`, `current` and `next` character
to make decisions. For a named utility one of the rules is that a `.`
must be preceded by and followed by a digit. This can be written as:

```rs
match (cursor.prev, cursor.curr, cursor.next) {
  (b'0'..=b'9', b'.', b'0'..=b'9') => { /* … */ }
  _ => { /* … */ }
}
```

But this is not very fast because Rust can't optimize the match
statement very well, especially because we are dealing with tuples
containing 3 values and each value is a `u8`.

To solve this we use some nesting, once we reach `b'.'` only then will
we check for the previous and next characters. We will also early return
in most places. If the previous character is not a digit, there is no
need to check the next character.

**Classification and jump tables:**

Another optimization we did is to classify the characters into a much
smaller `enum` such that Rust _can_ optimize all `match` arms and create
some jump tables behind the scenes.

E.g.:

```rs
#[derive(Debug, Clone, Copy, PartialEq)]
enum Class {
    /// ', ", or `
    Quote,

    /// \
    Escape,

    /// Whitespace characters
    Whitespace,

    Other,
}

const CLASS_TABLE: [Class; 256] = {
    let mut table = [Class::Other; 256];

    macro_rules! set {
        ($class:expr, $($byte:expr),+ $(,)?) => {
            $(table[$byte as usize] = $class;)+
        };
    }

    set!(Class::Quote, b'"', b'\'', b'`');
    set!(Class::Escape, b'\\');
    set!(Class::Whitespace, b' ', b'\t', b'\n', b'\r', b'\x0C');

    table
};
```

There are only 4 values in this enum, so Rust can optimize this very
well. The `CLASS_TABLE` is generated at compile time and must be exactly
256 elements long to fit all `u8` values.

**Inlining**:

Last but not least, sometimes we use functions to abstract some logic.
Luckily Rust will optimize and inline most of the functions
automatically. In some scenarios, explicitly adding a
`#[inline(always)]` improves performance, sometimes it doesn't improve
it at all.

You might notice that in some functions the annotation is added and in
some it's not. Every state machine was tested on its own and whenever
the performance was better with the annotation, it was added.

### Test Plan

1. Each machine has a dedicated set of tests to try and extract the
relevant part for that machine. Most machines don't even check boundary
characters or try to extract nested candidates. So keep that in mind
when adding new tests. Extracting inside of nested `[…]` is only handled
by the outer most `extractor/mod.rs`.
2. The main `extractor/mod.rs` has dedicated tests for recent bug
reports related to missing candidates.
3. You can test each machine's performance if you want to.

There is a chance that this new parser is missing candidates even though
a lot of tests are added and existing tests have been ported.

To double check, we ran the new extractor on our own projects to make
sure we didn't miss anything obvious.

#### Tailwind UI

On Tailwind UI the diff looks like this:

<details>

<summary>diff</summary>

```diff
diff --git a/./main.css b/./pr.css
index d83b0a506..b3dd94a1d 100644
--- a/./main.css
+++ b/./pr.css
@@ -5576,9 +5576,6 @@ @layer utilities {
     --tw-saturate: saturate(0%);
     filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
   }
-  .\!filter {
-    filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,) !important;
-  }
   .filter {
     filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
   }
```

</details>

The reason `!filter` is gone, is because it was used like this:

```js
getProducts.js
23:          if (!filter) return true
```

And right now `(` and `)` are not considered valid boundary characters
for a candidate.

#### Catalyst

On Catalyst, the diff looks like this:

<details>

<summary>diff</summary>

```diff
diff --git a/./main.css b/./pr.css
index 9f8ed129..4aec992e 100644
--- a/./main.css
+++ b/./pr.css
@@ -2105,9 +2105,6 @@
   .outline-transparent {
     outline-color: transparent;
   }
-  .filter {
-    filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
-  }
   .backdrop-blur-\[6px\] {
     --tw-backdrop-blur: blur(6px);
     -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
@@ -7141,46 +7138,6 @@
   inherits: false;
   initial-value: solid;
 }
-@property --tw-blur {
-  syntax: "*";
-  inherits: false;
-}
-@property --tw-brightness {
-  syntax: "*";
-  inherits: false;
-}
-@property --tw-contrast {
-  syntax: "*";
-  inherits: false;
-}
-@property --tw-grayscale {
-  syntax: "*";
-  inherits: false;
-}
-@property --tw-hue-rotate {
-  syntax: "*";
-  inherits: false;
-}
-@property --tw-invert {
-  syntax: "*";
-  inherits: false;
-}
-@property --tw-opacity {
-  syntax: "*";
-  inherits: false;
-}
-@property --tw-saturate {
-  syntax: "*";
-  inherits: false;
-}
-@property --tw-sepia {
-  syntax: "*";
-  inherits: false;
-}
-@property --tw-drop-shadow {
-  syntax: "*";
-  inherits: false;
-}
 @property --tw-backdrop-blur {
   syntax: "*";
   inherits: false;
```

</details>

The reason for this is that `filter` was only used as a function call:

```tsx
src/app/docs/Code.tsx
31:    .filter((x) => x !== null)
```

This was tested on all templates and they all remove a very small amount
of classes that aren't used.

The script to test this looks like this:

```sh
bun --bun ~/github.com/tailwindlabs/tailwindcss/packages/@tailwindcss-cli/src/index.t -- -i ./src/styles/tailwind.css -o pr.css
bun --bun ~/github.com/tailwindlabs/tailwindcss--main/packages/@tailwindcss-cli/src/index.t -- -i ./src/styles/tailwind.css -o main.css

git diff --no-index --patch ./{main,pr}.css
```

This is using git worktrees, so the `pr` branch lives in a `tailwindcss`
folder, and the `main` branch lives in a `tailwindcss--main` folder.

---

### Fixes:

- Fixes: https://github.com/tailwindlabs/tailwindcss/issues/15616
- Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16750
- Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16790
- Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16801
- Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16880 (due
to validating the arbitrary property)

---

### Ideas for in the future

1. Right now each machine takes in a `Cursor` object. One potential
improvement we can make is to rely on the `input` on its own instead of
going via the wrapping `Cursor` object.
2. If you take a look at the AST, you'll notice that utilities and
variants have a "root", these are basically prefixes of each available
utility and/or variant. We can use this information to filter out
candidates and bail out early if we know that a certain candidate will
never produce a valid class.
3. Passthrough the `prefix` information. Everything that doesn't start
with `tw:` can be skipped.

### Design decisions that didn't make it

Once you reach this part, you can stop reading if you want to, but this
is more like a brain dump of the things we tried and didn't work out.
Wanted to include them as a reference in case we want to look back at
this issue and know _why_ certain things are implemented the way they
are.

#### One character at a time

In an earlier implementation, the state machines were pure state
machines where the `next()` function was called on every single
character of the input. This had a lot of overhead because for every
character we had to:

1. Ask the `CandidateMachine` which state it was in.
2. Check the `cursor.curr` (and potentially the `cursor.prev` and
`cursor.next`) character.
3. If we were in a state where a nested state machine was running, we
had to check its current state as well and so on.
4. Once we did all of that we could go to the next character.

In this approach, the `MachineState` looked like this instead:

```rs
enum MachineState {
  Idle,
  Parsing,
  Done(Span)
}
```

This had its own set of problems because now it's very hard to know
whether we are done or not.

```html
<div class="hover:flex"></div>
<!--            ^          -->
```

Let's look at the current position in the example above. At this point,
it's both a valid variant and valid utility, so there was a lot of
additional state we had to track to know whether we were done or not.

#### `Span` stitching

Another approach we tried was to just collect all valid variants and
utilities and throw them in a big `Vec<Span>`. This reduced the amount
of additional state to track and we could track a span the moment we saw
a `MachineState::Done(span)`.

The next thing we had to do was to make sure that:

1. Covered spans were removed. We still do this part in the current
implementation.
2. Combine all touching variant spans (where `span_a.end + 1 ==
span_b.start`).
3. For every combined variant span, find a corresponding utility span.
   - If there is no utility span, the candidate is invalid.
- If there are multiple candidate spans (this is in theory not possible
because we dropped covered spans)
- If there is a candidate _but_ it is attached to another set of spans,
then the candidate is invalid. E.g.: `flex!block`
4. All left-over utility spans are candidates without variants.

This approach was slow, and still a bit hard to reason about.

#### Matching on tuples

While matching against the `prev`, `curr` and `next` characters was very
readable and easy to reason about. It was not very fast. Unfortunately
had to abandon this approach in favor of a more optimized approach.

In a perfect world, we would still write it this way, but have some
compile time macro that would optimize this for us.

#### Matching against `b'…'` instead of classification and jump tables

Similar to the previous point, while this is better for readability,
it's not fast enough. The jump tables are much faster.

Luckily for us, each machine has it's own set of rules and context, so
it's much easier to reason about a single problem and optimize a single
machine.

[^candidate]: A candidate is what a potential Tailwind CSS class _could_
be. It's a candidate because at this stage we don't know if it will
actually produce something but it looks like it could be a valid class.
E.g.: `hover:bg-red-500` is a candidate, but it will only produce
something if `--color-red-500` is defined in your theme.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-05 11:55:24 +01:00
Sơn Trần-Nguyễn
781fb73b7e
doc(CHANGELOG): correct PR number for scripting (#16947)
Tiny correction of the CHANEGLOG
2025-03-04 16:45:59 +00:00
Robin Malfait
57b080c5f2
Allow !important on CSS variables (#16873)
We recently fixed an issue
(https://github.com/tailwindlabs/tailwindcss/issues/16664) so that we
could remove the `!important` on CSS variables. The reason we did that
is because Google Chrome is showing a warning in the devtools styles
panel that this is invalid. However, this _is_ valid and the code
actually works as expected.

If we look at the CSS spec for this:

> Note: Custom properties can contain a trailing `!important`, but this
is automatically removed from the property’s value by the CSS parser,
and makes the custom property "important" in the CSS cascade. In other
words, the prohibition on top-level "!" characters does not prevent
`!important` from being used, as the `!important` is removed before
syntax checking happens.
>
> — https://www.w3.org/TR/css-variables-1/#syntax


So given this input:
```css
@import "tailwindcss";

body {
  --text-color: var(--color-white) !important;
  --bg-color: var(--color-blue-950) !important;

  /* Direct usage */
  background-color: var(--bg-color);

  /* Usage inside other functions as-if the `!important` is in the middle instead of the end */
  color: color-mix(in oklab, var(--text-color) 75%, transparent);
}
```

You will notice that everything works as expected, but if you look at
the Styles panel in Chrome for the `<body>` element, you will see an
incorrect warning. (At least that's what you used to see, I updated
Chrome and everything renders fine in devtools).

Play: https://play.tailwindcss.com/BpjAJ6Uxg3?file=css

This change reverts the "fix" for:
https://github.com/tailwindlabs/tailwindcss/issues/16664. @winchesHe you
were the original person that opened the issue so this info might be
useful to you as well. Can you verify that the Play link above does work
as expected for you?

Fixes: #16810
2025-03-04 16:21:56 +00:00
Philipp Spiess
db405304f4
Don't swallow @utility declarations when @apply is used in nested rules (#16940)
Fixes #16935

This PR fixes an issue where the order of how `@apply` was resolved was
incorrect for nested rules. Consider this example:

```css
.rule {
  @apply underline;
  .nested-rule {
    @apply custom-utility;
  }
}

@utility custom-utility {
  @apply flex;
}
```

The way we topologically sort these, we end up with a list that looks
roughly like this:

```css
.rule {
  @apply underline;
  .nested-rule {
    @apply custom-utility;
  }
}

@utility custom-utility {
  @apply flex;
}

.nested-rule {
  @apply custom-utility;
}
```

As you can see here the nested rule is now part of the top-level list.
This is correct because we first have to substitute the `@apply` inside
the `@utility custom-utility` before we can apply the `custom-utility`
inside `.nested-rule`. However, because we were using a regular AST walk
and because the initial `.rule` also contains the `.nested-rule` as
child, we would first substitute the `@apply` inside the `.nested-rule`,
causing the design-system to force resolve (and cache) the wrong value
for `custom-utility`.

Because the list is already flattened, we do not need to recursively
look into child declarations when we traverse the sorted list. This PR
changes it to use a regular `for` loop instead of the `walk`.

## Test plan

- Added a regression test
- Rest of tests still green
2025-03-04 16:54:05 +01:00
Philipp Spiess
b0aa20c30e
Ensure outline-hidden behaves like outline-none in non-forced-colors mode (#16943)
Part-of #16926

I noticed that `outline-hidden` would not set `--tw-outline-style`
(contrary to `outline-none`), thus stacking it with other outline
classes won't work as expected: https://play.tailwindcss.com/Y0lPGgekYh
2025-03-04 16:36:28 +01:00
depfu[bot]
a98ebacc64
Update prettier-plugin-embed 0.4.15 → 0.5.0 (major) (#16906)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ prettier-plugin-embed (0.4.15 → 0.5.0) ·
[Repo](https://github.com/Sec-ant/prettier-plugin-embed) ·
[Changelog](https://github.com/Sec-ant/prettier-plugin-embed/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/Sec-ant/prettier-plugin-embed/releases/tag/v0.5.0">0.5.0</a></h4>

<blockquote><h3 dir="auto">Minor Changes</h3>
<ul dir="auto">
<li>
<a
href="15dd288ebc"><tt>15dd288</tt></a>:
feat: support shorthand for CallExpression as tag</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="cbbbfde47c...488a11218e">See
the full diff on Github</a>. The new version differs by 28 commits:</p>
<ul>
<li><a
href="488a11218e"><code>chore(release):
v0.5.0 (#140)</code></a></li>
<li><a
href="7ee0ab0046"><code>docs:
add JounQin as a contributor for code (#145)</code></a></li>
<li><a
href="89d8417a21"><code>chore:
renovate ignore chevrotain</code></a></li>
<li><a
href="0935722d21"><code>chore:
use pnpm (#141)</code></a></li>
<li><a
href="0393ef18e2"><code>ci:
remove broken biome-cli-codesandbox</code></a></li>
<li><a
href="15dd288ebc"><code>feat:
support shorthand for CallExpression as tag</code></a></li>
<li><a
href="d8b1faeef5"><code>chore(deps-dev):
bump the minor-and-patch-updates group with 2 updates</code></a></li>
<li><a
href="440316d9a8"><code>chore(deps):
bump the minor-and-patch-updates group with 8 updates</code></a></li>
<li><a
href="92330b8c4e"><code>chore(deps-dev):
bump the minor-and-patch-updates group with 6 updates</code></a></li>
<li><a
href="844ac42831"><code>chore(deps-dev):
bump the minor-and-patch-updates group with 4 updates</code></a></li>
<li><a
href="4d53e3a08f"><code>chore(deps):
bump the minor-and-patch-updates group with 7 updates</code></a></li>
<li><a
href="1c5b658cef"><code>chore(deps):
bump the minor-and-patch-updates group with 7 updates</code></a></li>
<li><a
href="35674aeb28"><code>chore(deps-dev):
bump the minor-and-patch-updates group with 5 updates</code></a></li>
<li><a
href="952153d8dc"><code>chore(deps):
bump the minor-and-patch-updates group with 6 updates</code></a></li>
<li><a
href="62b4f229f4"><code>chore(deps-dev):
bump the minor-and-patch-updates group with 4 updates</code></a></li>
<li><a
href="d7d1f91cce"><code>chore(deps):
bump the minor-and-patch-updates group with 5 updates</code></a></li>
<li><a
href="9258b67dd9"><code>chore(deps):
bump the minor-and-patch-updates group with 8 updates</code></a></li>
<li><a
href="aa02346f3f"><code>chore(deps):
bump the minor-and-patch-updates group with 8 updates</code></a></li>
<li><a
href="201cd80bca"><code>chore(deps-dev):
bump the minor-and-patch-updates group with 2 updates</code></a></li>
<li><a
href="dd39ad0040"><code>chore(deps):
bump the minor-and-patch-updates group with 9 updates</code></a></li>
<li><a
href="d663923f48"><code>chore(deps):
bump the minor-and-patch-updates group with 7 updates</code></a></li>
<li><a
href="f17fc0d0e5"><code>chore(deps-dev):
bump the minor-and-patch-updates group with 7 updates</code></a></li>
<li><a
href="7b4bf7fb04"><code>chore(deps):
bump the minor-and-patch-updates group with 6 updates</code></a></li>
<li><a
href="10846661c3"><code>chore(deps-dev):
bump the minor-and-patch-updates group with 11 updates</code></a></li>
<li><a
href="2a16f76b13"><code>chore(deps-dev):
bump the minor-and-patch-updates group with 1 update</code></a></li>
<li><a
href="6db029d50e"><code>chore:
bump deps</code></a></li>
<li><a
href="ef876ba3f8"><code>chore(deps-dev):
bump the major-updates group with 2 updates</code></a></li>
<li><a
href="e8d629826a"><code>chore(deps-dev):
bump the minor-and-patch-updates group with 2 updates</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-03-03 13:22:51 +01:00
Adam Wathan
595b88f271
Support bare col-* and row-* utilities (#15183)
Resolves #15170.

This PR adds support for bare integer values to the `col-*` and `row-*`
utilities:

```css
.col-5 {
  grid-column: 5;
}
.row-6 {
  grid-row: 6;
}
```

These properties are shorthands for
`grid-column-start`/`grid-column-end` and
`grid-row-start`/`grid-row-end`, so using a bare integer value ends up
being a shortcut for:

```css
.col-5 {
  grid-column-start: 5;
  grid-column-end: auto;
}
```

…which makes these basically work like an alternative to `col-start-*`
and `row-start-*`.

These support negative values like `-col-6` as well, which also
technically extends to arbitrary values like `-col-[6/span_2]` now even
though that is a junk value. I've decided not to guard against that
though and just consider it user error to keep the implementation
simple.

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-02-27 23:58:24 +01:00
Philipp Spiess
66ef77ce76
Emit @keyframes in prefixed setup (#16850)
Closes #16829

We were only adding keyframes used in variables starting with
`--animate` (without adding a potential prefix).

## Test plan

- Added a unit test
2025-02-27 17:35:25 +01:00
Luke Warlow
5532d48e8e
Add scripting variants (#11929)
This adds two variants for the
[`scripting`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/scripting)
media query. `noscript` for when JavaScript is disabled and `scripting`
for when it's enabled.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-02-26 18:43:24 +01:00
Philipp Spiess
e938c58778
Ensure not-* does not remove :is(…) (#16825)
Resolves #16821

This PR removes a special case in the `not-*` variant compound that
removed `:is(…)` if it was the only part of the inversed selector list.
While in-theory this makes sense, `:is(…)` accepts a _forgiving_
selector list where as `:not(…)` does not. See the [last point
here](https://developer.mozilla.org/en-US/docs/Web/CSS/:not#description).

This is an issue specifically in combinations with variants that have
selectors that are not supported by all browsers yet, for example
`:open`.

It seems to be the most expected to simply keep the `:is(…)` here in any
case.

## Test plan
- Ensured the repro form #16821 now also works in browsers that do not
support `:open` (Safari and Firefox at the time of writing this):
<img width="484" alt="Screenshot 2025-02-26 at 15 36 22"
src="https://github.com/user-attachments/assets/f3391693-895b-4e44-8566-95e2960ec4e3"
/>
2025-02-26 18:03:11 +01:00
Luke Warlow
124b82bc79
Add details-content variant (#15319)
This matches the new `::details-content` pseudo element.

See
https://developer.chrome.com/blog/styling-details#the_details-content_pseudo
for more info.

Currently shipped in Chromium, in development in WebKit and no
development yet in Firefox but they're
[supportive](https://github.com/mozilla/standards-positions/issues/1027).

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-02-26 11:36:32 +01:00
Benoît Rouleau
ab9df29551
Fix link in CHANGELOG.md (#16806)
The proper PR for this changelog entry is #16800.
2025-02-25 22:12:14 +01:00
Philipp Spiess
a8a2a43f6a Pin pnpm version in freebsd build 2025-02-25 18:20:14 +01:00
Philipp Spiess
6fffcfd377 Run pnpm install first in freebsd build 2025-02-25 17:50:27 +01:00
Philipp Spiess
a7d8371c71 Require build-freebsd too 2025-02-25 17:44:05 +01:00
Philipp Spiess
85d7375b59
Prepare v4.0.9 release (#16804)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-02-25 17:33:28 +01:00
Philipp Spiess
b56f12e6e7
Ensure nested functions in selectors used with JavaScript plugins are not truncated (#16802)
Fixes #16799

This was caused by a wrong condition in the CSS value parser that put
child function arguments into the parent function by accident.

## Test plan

- Added a unit test to guard against regressions
- Validated against the repro:  
<img width="914" alt="Screenshot 2025-02-25 at 16 31 14"
src="https://github.com/user-attachments/assets/f5fdf2e7-9c1b-4b04-89a8-1fa78a27f0f5"
/>
2025-02-25 17:00:55 +01:00
Philipp Spiess
294952f170
Handle BOM (#16800)
Resolves #15662 
Resolves #15467

## Test plan

Added integration tests for upgrade tooling (which already worked
surprisingly?) and CLI.
2025-02-25 16:07:16 +01:00
Jordan Pittman
662c6862ac
Make JS APIs available to plugins and configs in the Standalone CLI (#15934)
This PR ensures we bundle the relevant JS APIs in the Standalone CLI
like `tailwindcss`, `tailwindcss/plugin`, `tailwindcss/colors`, etc…

Before, when loading plugins or configs, imports for those resources
would fail.

Fixes #15235

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-02-25 13:21:46 +01:00
Philipp Spiess
ef57e6ea4d
Fix z-*! utilities (#16795)
Closes #16794
2025-02-25 12:20:11 +01:00
Philipp Spiess
b38948337d
Make @reference emit variable fallbacks instead of CSS variable declarations (#16774)
Fixes #16725

When using `@reference "tailwindcss";` inside a separate CSS root (e.g.
Svelte `<style>` components, CSS modules, etc.), we have no guarantee
that the CSS variables will be defined in the main stylesheet (or if
there even is one). To work around potential issues with this we decided
in #16676 that we would emit all used CSS variables from the `@theme`
inside the `@reference` block.

However, this is not only a bit surprising but also unexpected in CSS
modules and Next.js that **requires CSS module files to only create
scope-able declarations**. To fix this issue, we decided to not emit CSS
variables but instead ensure all `var(…)` calls we create for theme
values in reference mode will simply have their fallback value added.

This ensures styles work as-expected even if the root Tailwind file does
not pick up the variable as being used or _if you don't add a root at
all_. Furthermore we do not duplicate any variable declarations across
your stylesheets and you still have the ability to change variables at
runtime.

## Test plan

- Updated snapshots everywhere (see diff)
- New Next.js CSS modules integration test
2025-02-25 11:36:43 +01:00
Philipp Spiess
59e003e6d1
Vite: Don't crash with virtual module dependencies (#16780)
Fixes #16732

If we can not get the mtime from a file, chances are that the resource
is a virtual module. This is perfectly legit and we can fall back to
what we did before the changes in `4.0.8` (which is to rebuild the root
every time a change contains a dependency like that).

## Test plan

Added a test to mimic the setup from the repor in #16732. Also ensured
the repro now passes:

<img width="1278" alt="Screenshot 2025-02-24 at 17 29 38"
src="https://github.com/user-attachments/assets/d111273d-579f-44c2-82f5-aa32d6a1879a"
/>

Note that importing virtual modules directly in CSS does not work as the
resolver we use does not resolve against the Vite runtime it seems. This
is unrelated to the regression added in `4.0.8` though and something to
look into in the future.
2025-02-25 11:29:58 +01:00
depfu[bot]
37ea0c3493
Update @types/react 19.0.8 → 19.0.10 (patch) (#16775) 2025-02-24 13:21:52 +01:00
depfu[bot]
a893de22ba
Update @types/react-dom 19.0.3 → 19.0.4 (patch) (#16773) 2025-02-24 13:06:25 +01:00
depfu[bot]
604be562e3
Update eslint 9.19.0 → 9.20.1 (minor) (#16726)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.19.0 → 9.20.1) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.20.1">9.20.1</a></h4>

<blockquote><h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="274f054f19"><code
class="notranslate">274f054</code></a> fix: fix <code
class="notranslate">RuleContext</code> type (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19417">#19417</a>)
(Francesco Trotta)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="fe3ccb2ff4"><code
class="notranslate">fe3ccb2</code></a> docs: allow typing in search box
while dropdown is open (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19424">#19424</a>)
(Amaresh S M)</li>
<li>
<a
href="93c78a5c58"><code
class="notranslate">93c78a5</code></a> docs: Add instructions for pnpm
compat (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19422">#19422</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="b476a930bb"><code
class="notranslate">b476a93</code></a> docs: Fix Keyboard Navigation for
Search Results (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19416">#19416</a>)
(Amaresh S M)</li>
<li>
<a
href="ccb60c0b14"><code
class="notranslate">ccb60c0</code></a> docs: Update README (GitHub
Actions Bot)</li>
</ul></blockquote>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.20.0">9.20.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="e89a54a309"><code
class="notranslate">e89a54a</code></a> feat: change behavior of inactive
flags (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19386">#19386</a>)
(Milos Djermanovic)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="91d4d9f620"><code
class="notranslate">91d4d9f</code></a> fix: Bring types in sync with
@eslint/core (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19157">#19157</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="fa25c7a79e"><code
class="notranslate">fa25c7a</code></a> fix: Emit warning when empty
config file is used (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19399">#19399</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="31a9fd03d2"><code
class="notranslate">31a9fd0</code></a> fix: Clearer error message for
wrong plugin format (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19380">#19380</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="61d99e38f2"><code
class="notranslate">61d99e3</code></a> fix: Better error message for
unserializable parser (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19384">#19384</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="db1b9a66e3"><code
class="notranslate">db1b9a6</code></a> fix: Ensure module scope is
checked for references in <code
class="notranslate">consistent-this</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19383">#19383</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="8bcd820f37"><code
class="notranslate">8bcd820</code></a> fix: <code
class="notranslate">arrow-body-style</code> crash with single-token body
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19379">#19379</a>)
(Milos Djermanovic)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="b7012c85f3"><code
class="notranslate">b7012c8</code></a> docs: rewrite examples with var
using let and const (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19407">#19407</a>)
(Mueez Javaid Hashmi)</li>
<li>
<a
href="64063765af"><code
class="notranslate">6406376</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="350f2b9349"><code
class="notranslate">350f2b9</code></a> docs: rewrite some examples with
var using let and const (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19404">#19404</a>)
(Mueez Javaid Hashmi)</li>
<li>
<a
href="93c325a7a8"><code
class="notranslate">93c325a</code></a> docs: rewrite examples with var
using let and const (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19398">#19398</a>)
(Mueez Javaid Hashmi)</li>
<li>
<a
href="56ff4048e0"><code
class="notranslate">56ff404</code></a> docs: replace var with let or
const in rules docs (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19396">#19396</a>)
(Daniel Harbrueger)</li>
<li>
<a
href="4053226996"><code
class="notranslate">4053226</code></a> docs: change <code
class="notranslate">sourceType</code> in <code
class="notranslate">no-eval</code> examples (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19393">#19393</a>)
(Milos Djermanovic)</li>
<li>
<a
href="1324af0279"><code
class="notranslate">1324af0</code></a> docs: replace var with let and
const in rules docs (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19392">#19392</a>)
(Daniel Harbrueger)</li>
<li>
<a
href="8b87e007bb"><code
class="notranslate">8b87e00</code></a> docs: replace <code
class="notranslate">var</code> with <code
class="notranslate">const</code> and <code
class="notranslate">let</code> in rules (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19389">#19389</a>)
(Tanuj Kanti)</li>
<li>
<a
href="758c66bc8d"><code
class="notranslate">758c66b</code></a> docs: Explain what frozen rules
mean (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19382">#19382</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="0ef8bb859c"><code
class="notranslate">0ef8bb8</code></a> docs: additional checks for rule
examples (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19358">#19358</a>)
(Milos Djermanovic)</li>
<li>
<a
href="58ab2f69d2"><code
class="notranslate">58ab2f6</code></a> docs: fix order of installation
steps in getting started (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19326">#19326</a>)
(Tanuj Kanti)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="979097a3b4"><code
class="notranslate">979097a</code></a> chore: upgrade @eslint/js@9.20.0
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19412">#19412</a>)
(Francesco Trotta)</li>
<li>
<a
href="031734efcb"><code
class="notranslate">031734e</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="d4c47c3738"><code
class="notranslate">d4c47c3</code></a> test: avoid empty config warning
in test output (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19408">#19408</a>)
(Milos Djermanovic)</li>
<li>
<a
href="67dd82ab88"><code
class="notranslate">67dd82a</code></a> chore: update dependency
@eslint/json to ^0.10.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19387">#19387</a>)
(renovate[bot])</li>
<li>
<a
href="15ac0e1824"><code
class="notranslate">15ac0e1</code></a> chore: add permissions: read-all
to stale.yml workflow (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19374">#19374</a>)
(Josh Goldberg ✨)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="208e0b199f...07b2ffd3c5">See
the full diff on Github</a>. The new version differs by 32 commits:</p>
<ul>
<li><a
href="07b2ffd3c5"><code>9.20.1</code></a></li>
<li><a
href="01ff142d55"><code>Build:
changelog update for 9.20.1</code></a></li>
<li><a
href="fe3ccb2ff4"><code>docs:
allow typing in search box while dropdown is open
(#19424)</code></a></li>
<li><a
href="274f054f19"><code>fix:
fix `RuleContext` type (#19417)</code></a></li>
<li><a
href="93c78a5c58"><code>docs:
Add instructions for pnpm compat (#19422)</code></a></li>
<li><a
href="b476a930bb"><code>docs:
Fix Keyboard Navigation for Search Results (#19416)</code></a></li>
<li><a
href="ccb60c0b14"><code>docs:
Update README</code></a></li>
<li><a
href="7e78b50dac"><code>9.20.0</code></a></li>
<li><a
href="588b664536"><code>Build:
changelog update for 9.20.0</code></a></li>
<li><a
href="979097a3b4"><code>chore:
upgrade @eslint/js@9.20.0 (#19412)</code></a></li>
<li><a
href="031734efcb"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="91d4d9f620"><code>fix:
Bring types in sync with @eslint/core (#19157)</code></a></li>
<li><a
href="b7012c85f3"><code>docs:
rewrite examples with var using let and const (#19407)</code></a></li>
<li><a
href="d4c47c3738"><code>test:
avoid empty config warning in test output (#19408)</code></a></li>
<li><a
href="e89a54a309"><code>feat:
change behavior of inactive flags (#19386)</code></a></li>
<li><a
href="fa25c7a79e"><code>fix:
Emit warning when empty config file is used (#19399)</code></a></li>
<li><a
href="64063765af"><code>docs:
Update README</code></a></li>
<li><a
href="350f2b9349"><code>docs:
rewrite some examples with var using let and const
(#19404)</code></a></li>
<li><a
href="93c325a7a8"><code>docs:
rewrite examples with var using let and const (#19398)</code></a></li>
<li><a
href="56ff4048e0"><code>docs:
replace var with let or const in rules docs (#19396)</code></a></li>
<li><a
href="4053226996"><code>docs:
change `sourceType` in `no-eval` examples (#19393)</code></a></li>
<li><a
href="1324af0279"><code>docs:
replace var with let and const in rules docs (#19392)</code></a></li>
<li><a
href="8b87e007bb"><code>docs:
replace `var` with `const` and `let` in rules (#19389)</code></a></li>
<li><a
href="31a9fd03d2"><code>fix:
Clearer error message for wrong plugin format (#19380)</code></a></li>
<li><a
href="61d99e38f2"><code>fix:
Better error message for unserializable parser (#19384)</code></a></li>
<li><a
href="758c66bc8d"><code>docs:
Explain what frozen rules mean (#19382)</code></a></li>
<li><a
href="67dd82ab88"><code>chore:
update dependency @eslint/json to ^0.10.0 (#19387)</code></a></li>
<li><a
href="db1b9a66e3"><code>fix:
Ensure module scope is checked for references in `consistent-this`
(#19383)</code></a></li>
<li><a
href="8bcd820f37"><code>fix:
`arrow-body-style` crash with single-token body (#19379)</code></a></li>
<li><a
href="15ac0e1824"><code>chore:
add permissions: read-all to stale.yml workflow (#19374)</code></a></li>
<li><a
href="0ef8bb859c"><code>docs:
additional checks for rule examples (#19358)</code></a></li>
<li><a
href="58ab2f69d2"><code>docs:
fix order of installation steps in getting started
(#19326)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-02-21 21:06:10 +01:00
Luke Warlow
751eb747d4
Add inverted-colors variant (#11693)
Add a variant for the
[`inverted-colors`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/inverted-colors)
media query.

This has been supported in Safari for a while. I'm also implementing
support in Chrome atm.

I've decided to only add the inverted-colors: inverted variant because
the `none` state isn't really useful.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-02-21 18:03:52 +01:00
SerKo
62d3e74694
Add wrap-anywhere utility (#12128)
Related discussion #12127

```css
.wrap-anywhere {
  overflow-wrap: anywhere;
}
```

### Updated 2024-11-30

The new changes remove the original `@supports` because I agree that
developers should decide for themselves whether to maintain backward
compatibility. Also updated in line with the new changes in the `next`
branch.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-02-21 16:49:05 +01:00
Philipp Spiess
419b3dc473
Prepare v4.0.8 release (#16713)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create an issue to first
discuss any significant new features. This includes things like adding
new utilities, creating new at-rules, or adding new component examples
to the documentation.


https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md

-->

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-02-21 16:06:37 +01:00
Robin Malfait
b47b6d2290
Remove double translate-z-px values (#16718)
This PR fixes an issue where if you used `translate-z-px` or
`-translate-z-px` that the utility was generated twice:
```css
.translate-z-px {
  --tw-translate-z: 1px;
  translate: var(--tw-translate-x) var(--tw-translate-y) var(--tw-translate-z);
}
.translate-z-px {
  --tw-translate-z: 1px;
  translate: var(--tw-translate-x) var(--tw-translate-y) var(--tw-translate-z);
}
@property --tw-translate-x {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
@property --tw-translate-y {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
@property --tw-translate-z {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
```

With this PR, it will only generate once:
```css
.translate-z-px {
  --tw-translate-z: 1px;
  translate: var(--tw-translate-x) var(--tw-translate-y) var(--tw-translate-z);
}
@property --tw-translate-x {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
@property --tw-translate-y {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
@property --tw-translate-z {
  syntax: "*";
  inherits: false;
  initial-value: 0;
}
```
2025-02-21 14:08:46 +00:00
Robin Malfait
113142a0e4
Use amount of properties when sorting (#16715)
Right now we sort the nodes based on a pre-defined sort order based on
the properties that are being used. The property sort order is defined
in a list we maintain.

We also have to make sure that the property count is taken into account
such that if all the "sorts" are the same, that we fallback to the
property count. Most amount of properties should be first such that we
can override it with more specific utilities that have fewer properties.

However, if a property doesn't exist, then it wouldn't be included in a
list of properties therefore the total count was off.

This PR fixes that by counting all the used properties. If a property
already exists it is counted twice. E.g.:
```css
.foo {
  color: red;

  &:hover {
    color: blue;
  }
}
```

In this case, we have 2 properties, not 1 even though it's the same
`color` property.

## Test plan:

1. Updated the tests that are now sorted correctly
2. Added an integration test to make sure that `prose-invert` is defined
after the `prose-stone` classes when using the `@tailwindcss/typography`
plugin where this problem originated from.

Note how in this play (https://play.tailwindcss.com/wt3LYDaljN) the
`prose-invert` comes _before_ the `prose-stone` which means that you
can't apply the `prose-invert` classes to invert `prose-stone`.
2025-02-21 15:02:07 +01:00
Adam Wathan
f8d7623ea5
Preserve custom properties in keyframes (#16376)
Closes #16374

Ensure we don't remove custom properties from within `@keyframe`
declarations.

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-02-21 10:55:30 +01:00
Philipp Spiess
7bece4de7c
Re-enable: Only expose used CSS variables (#16676)
This PR re-enables the changes necessary to remove unused theme
variables and keyframes form your CSS.

This change was initially landed as #16211 and then later reverted in
#16403 because we found some unexpected interactions with using `@apply`
and CSS variables in multi-root setups like CSS modules or Vue inline
`<style>` blocks that were no longer seeing their required variables
defined.

This issue is fixed by now ensuring that theme variables that are
defined within an `@reference "…"` boundary will still be emitted in the
generated CSS when used (as this would otherwise not generate a valid
stylesheet).

So given the following input CSS:

```css
@reference "tailwindcss";
.text-red {
  @apply text-red-500;
}
```

We will now compile this to:

```css
@layer theme {
  :root, :host {
    --text-red-500: oklch(0.637 0.237 25.331);
  }
}
.text-red {
  color: var(--text-red-500);
}
```

This PR also improves the initial implementation to not mark theme
variables as used if they are only used to define other theme variables.
For example:

```css
@theme {
  --font-sans:
    ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
    'Noto Color Emoji';
  --font-mono:
    ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
    monospace;

  --default-font-family: var(--font-sans);
  --default-mono-font-family: var(--font-mono);
}

.default-font-family {
  font-family: var(--default-font-family);
}
```

This would be reduced to the following now as `--font-mono` is only used
to define another variable and never used outside the theme block:

```css
:root, :host {
  --font-sans:
    ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
    'Noto Color Emoji';
  --default-font-family: var(--font-sans);
}

.default-font-family {
  font-family: var(--default-font-family);
}
```

## Test plan

- See updated unit and integration tests
- Validated it works end-to-end by using a SvelteKit example
2025-02-21 10:45:22 +01:00
depfu[bot]
dd7d8fd33a
Update turbo 2.3.4 → 2.4.2 (minor) (#16698)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ turbo (2.3.4 → 2.4.2) ·
[Repo](https://github.com/turborepo/turbo)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-02-21 10:21:08 +01:00
depfu[bot]
ec0049a3c2
Update @playwright/test 1.50.0 → 1.50.1 (patch) (#16702) 2025-02-21 10:13:03 +01:00
Philipp Spiess
1d56525fa0
Fix integration tests for Windows (#16693)
The Nuxt preview server always starts on port 3000 even if that port is
taken. With the added tests in #16631 there is now a higher chance these
ports are already taken since e.g. react router prefers to start at port
3000 and so do other servers.
This PR changes this so that we assign a random port inside the test
instead.

## Test plan

- Ensure Windows CI is green again
2025-02-20 17:50:50 +01:00
Robin Malfait
3f270d2d9b
Allow theme(…) options when using @import (#16514)
This PR improves the developer experience when trying to use `theme(…)`
options on an import.

Today, if you want to use Tailwind CSS, you can import it as:

```css
@import "tailwindcss";
```

But if you want to use any of the `theme(…)` options, like the `static`
theme option, then you had to use this instead:

```css
@layer theme, base, components, utilities;

@import 'tailwindcss/theme' layer(theme) theme(static);
@import 'tailwindcss/preflight' layer(base);
@import 'tailwindcss/utilities' layer(utilities);
```
In this scenario you have to be careful, because the `layer(…)` _must_
be the first option after an import (according to the spec). So if you
use `@import 'tailwindcss/theme' theme(static) layer(theme);` then
that's not going to work either.

This PR solves that by allowing you to use the `theme(…)` options
directly on the `@import` statement:

```css
@import 'tailwindcss' theme(static);
```

The only edge case is when you want to use `theme(reference)`. A typical
use case is for projects with `<style>` blocks where you want to
_reference_ the CSS variables from the theme.

If you use `@import 'tailwindcss' theme(reference);`, then all `@theme`
blocks will be references and you can reference theme values. This is
good. The bad part is that `@import 'tailwindcss';` also includes
preflight CSS. This means that we will import the preflight CSS for
every `<style>` block. This is probably not what you want.

The solution is to use `@reference 'tailwindcss';` instead which strips
all of that information and only gives you access to CSS variables.

This PR also makes sure that if you use `theme(reference)` on an import
that we still throw an error and suggest you use `@reference` instead.
This is not a breaking change because right now if you use `@import`
with `theme(…)` options it will already throw an error.

### Test plan:

1. Added dedicated tests to make sure we don't throw anymore.
2. Added test to make sure that we _do_ throw when using
`theme(reference)` on an import.
2025-02-20 15:57:16 +00:00
Philipp Spiess
88b762b539
Vite: Remove module-graph scanner (#16631)
Alternative to #16425

Fixes #16585
Fixes #16389
Fixes #16252
Fixes #15794
Fixes #16646
Fixes #16358

This PR changes the Vite plugin to use the file-system to discover
potential class names instead of relying on the module-graph. This comes
after a lot of testing and various issue reports where builds that span
different Vite instances were missing class names.

Because we now scan for candidates using the file-system, we can also
remove a lot of the bookkeeping necessary to make production builds and
development builds work as we no longer have to change the resulting
stylesheet based on the `transform` callbacks of other files that might
happen later.

This change comes at a small performance penalty that is noticeable
especially on very large projects with many files to scan. However, we
offset that change by fixing an issue that I found in the current Vite
integration that did a needless rebuild of the whole Tailwind root
whenever any source file changed. Because of how impactful this change
is, I expect many normal to medium sized projects to actually see a
performance improvement after these changes. Furthermore we do plan to
continue to use the module-graph to further improve the performance in
dev mode.

## Test plan

- Added new integration tests with cases found across the issues above.
- Manual testing by adding a local version of the Vite plugin to repos
from the issue list above and the [tailwindcss
playgrounds](https://github.com/philipp-spiess/tailwindcss-playgrounds).
2025-02-20 15:23:44 +01:00
Robin Malfait
b9af722d13
Fix remove !important on CSS variable declarations (#16668)
This PR fixes an issue where `!important` is added to declarations that
define CSS variables. The `!important` should only be added to the other
declarations.

Before:
```css
.ease-out\! {
  --tw-ease: var(--ease-out) !important;
  transition-timing-function: var(--ease-out) !important;
}
```

After:
```css
.ease-out\! {
  --tw-ease: var(--ease-out);
  transition-timing-function: var(--ease-out) !important;
}
```

Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16664
2025-02-19 17:20:09 +00:00
depfu[bot]
61af484ff4
Update all of nextjs 15.1.6 → 15.1.7 (patch) (#16654) 2025-02-19 13:16:11 +01:00
Philipp Spiess
541c3d2331
Prepare v4.0.7 release (#16629)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-02-18 16:51:20 +01:00
Philipp Spiess
23723e1e69
Statically link Visual Studio redistributables on Windows builds (#16602)
This PR statically links the C runtime on Windows builds so we no longer
require installing Visual Studio redistributables.

## Test plan

Relying on CI here to not break anything for Windows.
2025-02-18 13:45:04 +01:00
Philipp Spiess
ec1d7d4b85
Upgrade: Use latest tag for packages (#16620) 2025-02-18 11:57:51 +01:00
depfu[bot]
7ba4659fbe
Update postcss 8.5.1 → 8.5.2 (patch) (#16611) 2025-02-18 11:57:18 +01:00
Philipp Spiess
08972f294f
Scan Next.js dynamic route segments with manual @source rules (#16457)
Part of #16287

## Test plan

Added unit and integration tests

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-02-18 11:51:58 +01:00
Robin Malfait
f0141084c2
Pin exact versions of tailwindcss and @tailwindcss/* (#16623)
This PR fixes an issue where installing a specific version of
`@tailwindcss/postcss` and `tailwindcss` could still result in a version
mismatch. This is because we were relying on `^4.0.6` for example
instead of `4.0.6`.

This PR now pins all these versions to prevent this:
```
❯ pnpm why tailwindcss
devDependencies:
@tailwindcss/postcss 4.0.5
├─┬ @tailwindcss/node 4.0.6
│ └── tailwindcss 4.0.6
└── tailwindcss 4.0.5
```
2025-02-18 11:44:12 +01:00
Freeman
1c905f2adb
minor: better type safety and best practice to escape function (#16579) 2025-02-17 13:49:17 +01:00
depfu[bot]
8b1307614a
Update postcss-selector-parser 7.0.0 → 7.1.0 (minor) (#16558) 2025-02-17 12:07:13 +01:00
depfu[bot]
bbc2f3f6f1
Update globby 14.0.2 → 14.1.0 (minor) (#16564) 2025-02-17 12:06:53 +01:00
Philipp Spiess
c504f78e52 Update issue template 2025-02-17 11:25:38 +01:00
Philipp Spiess
14b13378cb
Update issue templates 2025-02-17 11:24:18 +01:00
Philipp Spiess
fadf442436
Update config.yml 2025-02-17 11:16:00 +01:00
Philipp Spiess
7d51e38d8c
Fix plugins with nested rules refering to the utility name (#16539)
Closes https://github.com/tailwindlabs/tailwindcss-typography/issues/383

This PR fixes an issue that happened when JavaScript plugins create a
nested rule that references to the utility name. The previous behavior
looked like this:


![image](https://github.com/user-attachments/assets/93ff869d-c95b-49d0-879c-7c20a852fa09)

I was able to come up with an approach that can be fixed entirely in the
compat layer by leveraging the `raw` field on the candidate.

## Test plan

- Added unit tests
- Verified with the reproduction from
https://github.com/tailwindlabs/tailwindcss-typography/issues/383:
      
<img width="1458" alt="Screenshot 2025-02-14 at 13 21 22"
src="https://github.com/user-attachments/assets/50544abc-e98f-48cd-b78c-ad7697387dd8"
/>
2025-02-14 18:14:14 +01:00
Philipp Spiess
a1e083afc5
Upgrade: Handle darkMode value with block syntax (#16507)
Closes #16171

This PR handles `darkMode` variant configs containing braces (so
creating sub-rules) the same way we handle it in the interop layer.
Since the interop layer runs inside the `addVariant` API that we do not
run here, I instead oped to copy the one liner.

## Test plan

Updated one of the migration tests to include a rule that wasn't working
before. Ensured the new output works via
https://play.tailwindcss.com/nR99uhKtv3
2025-02-14 16:48:52 +01:00
depfu[bot]
63b9be9483
Update h3 1.13.1 → 1.15.0 (minor) (#16515) 2025-02-14 11:44:43 +01:00
Philipp Spiess
dec6c8ca51
Upgrade: Report errors when updating dependencies (#16504)
Closes #16391

Like the title suggest this PR adds error reporting when the `npm
install` or `npm remove` commands fail.

## Test plan

Tested by swapping out the command for `echo "bla"; exit 1` and
capturing the output from the integration tests:

<img width="792" alt="Screenshot 2025-02-13 at 14 33 02"
src="https://github.com/user-attachments/assets/d1288114-106a-4ac6-a54b-d02b74c98f35"
/>

<img width="761" alt="Screenshot 2025-02-13 at 14 31 05"
src="https://github.com/user-attachments/assets/6d5b9427-457f-4e67-9723-4e340da61749"
/>

Decided not to add a new test for this since it's unlikely we'll do big
changes here and the upgrade integration tests are already quite slow.
2025-02-13 16:02:59 +01:00
Philipp Spiess
4f18f9099a
Add legacy type exports to tailwindcss/plugin export (#16505)
Closes #16209

This PR exposes the following types that were accessible via
`tailwindcss/types/config` in v3 now via the `tailwindcss/plugin`
export:

```ts
import type {Cofig, PluginAPI, PluginCreator, PluginsConfig, ThemeConfig } from 'tailwindcss/plugin'
```

Note that these types will not be the same as the v3 and just
approximations, however it should be enough to upgrade plugins to work
with v4.

## Test plan

Tested in a standalone project importing a dev build of tailwindcss: 

<img width="1784" alt="Screenshot 2025-02-13 at 14 50 48"
src="https://github.com/user-attachments/assets/27c04666-0106-414d-ba25-1a853f9d53d1"
/>
2025-02-13 15:52:55 +01:00
Philipp Spiess
6164783124
Emit --tw-drop-shadow property (#16471)
Fixes #16461

Ensure we also emit `@property --tw-drop-shadow`.

## Test plan

```
<div class="drop-shadow-2xl size-72 bg-white">
  <div class="size-48 saturate-100 bg-white"></div>
</div>
```

now only drops one shadow (screenshot from Vite playground):

<img width="562" alt="Screenshot 2025-02-12 at 16 37 25"
src="https://github.com/user-attachments/assets/94eaaf54-6fd5-4d10-9297-9e7523a02602"
/>
2025-02-12 18:36:03 +01:00
Philipp Spiess
53749c3725
Read from --default-outline-width (#16469)
Closes https://github.com/tailwindlabs/tailwindcss.com/issues/2073

This ensures that we can customize `outline` via
`--default-outline-width` just like `ring`, `border`, and other
utilities.

## Test plan

Added unit tests for `--default-outline-width` and
`--default-ring-width`
2025-02-12 18:16:30 +01:00
Martijn Cuppens
7326f64c41
Introduce :user-valid & :user-invalid variants (#12370)
See
- https://developer.mozilla.org/en-US/docs/Web/CSS/:user-valid
- https://developer.mozilla.org/en-US/docs/Web/CSS/:user-invalid

Just landed in Chrome, Firefox & Safari:
- https://caniuse.com/mdn-css_selectors_user-valid
- https://caniuse.com/mdn-css_selectors_user-invalid

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-02-12 17:17:54 +01:00
depfu[bot]
c80c0b5128
Update @types/bun 1.1.16 → 1.2.2 (minor) (#16444)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/bun (1.1.16 → 1.2.2) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-02-12 12:17:07 +01:00
Jordan Pittman
ff8fd8c9f7
Don’t replace _ in suggested theme keys (#16433)
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1184

The alpha and beta releases used `_` in theme keys to represent a `.`.
This meant we used `--leading-1_5` instead of `--leading-1\.5` to add
utilities like `leading-1.5`. We prefer the use of the escaped dot now
but still want to make sure suggestions for the legacy key format still
works as expected when surrounded by numbers.
2025-02-11 13:14:43 -05:00
Jordan Pittman
aad440ecf6
Show suggestions for fractions in IntelliSense (#16353)
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1178
(partially)

An IntelliSense PR (+ Play update) is required to make this work better
with v4:
https://github.com/tailwindlabs/tailwindcss-intellisense/pull/1182

<img width="671" alt="Screenshot 2025-02-07 at 15 19 59"
src="https://github.com/user-attachments/assets/7642b80c-f431-4a1e-964d-2a98ffe7a67a"
/>
2025-02-11 12:03:37 -05:00
depfu[bot]
f995dae5ca
Update enhanced-resolve 5.18.0 → 5.18.1 (patch) (#16439) 2025-02-11 18:02:29 +01:00
Paul Nodet
17c7c7ec30
fix(upgrade): prevent nextjs image blur property from modified (#16405)
Fix #16404

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-02-11 17:37:59 +01:00
depfu[bot]
f678a7025f
Update @types/react 19.0.7 → 19.0.8 (patch) (#16423) 2025-02-11 11:26:30 +01:00
Jordan Pittman
121cf6bd80
Fix sorting numeric utilities when they have different magnitudes (#16414)
When sorting utilities it was possible for a utility like `duration-700`
to come before `duration-75` or `duration-1000` to come before
`duration-150`.

This PR fixes this behavior by reading the full "magnitude" of each
numbers before we compare them.
2025-02-10 17:08:34 -05:00
Philipp Spiess
272c6dfa98
Add tailwindcss/lib/util/flattenColorPalette.js export (#16411)
Closes #16401

This adds a `tailwindcss/lib/util/flattenColorPalette.js` (notice the
`.js` suffix) inline with our other exports.
2025-02-10 23:01:26 +01:00
Robin Malfait
d045aaa75e
Prepare v4.0.6 (#16407) 2025-02-10 14:09:00 +01:00
Philipp Spiess
9bbe2e3d08
Revert: Only expose used CSS variables (#16403)
This reverts #16211

We found some unexpected interactions with using `@apply` and CSS
variables in multi-root setups like CSS modules or Vue inline `<style>`
blocks that were broken due to that change. We plan to re-enable this
soon and include a proper fix for those scenarios.

## Test plan

- Updated snapshots
- Tested using the CLI in a new project:
<img width="1523" alt="Screenshot 2025-02-10 at 13 08 42"
src="https://github.com/user-attachments/assets/defe0858-adb3-4d61-9d2c-87166558fd68"
/>

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-02-10 13:26:13 +01:00
depfu[bot]
1f84241f21
Update eslint 9.18.0 → 9.19.0 (minor) (#16357)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.18.0 → 9.19.0) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.19.0">9.19.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="1637b8e87d"><code
class="notranslate">1637b8e</code></a> feat: add <code
class="notranslate">--report-unused-inline-configs</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19201">#19201</a>)
(Josh Goldberg ✨)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="aae67172ab"><code
class="notranslate">aae6717</code></a> fix: sync rule type header
comments automatically (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19276">#19276</a>)
(Francesco Trotta)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="cfea9abe0e"><code
class="notranslate">cfea9ab</code></a> docs: Clarify overrideConfig
option (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19370">#19370</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="2b84f666cd"><code
class="notranslate">2b84f66</code></a> docs: Update README (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19362">#19362</a>)
(Nicholas C. Zakas)</li>
<li>
<a
href="044f93cbbe"><code
class="notranslate">044f93c</code></a> docs: clarify frozen rule
description (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19351">#19351</a>)
(Pavel)</li>
<li>
<a
href="797ee7c0d6"><code
class="notranslate">797ee7c</code></a> docs: fix Bluesky links (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19368">#19368</a>)
(Milos Djermanovic)</li>
<li>
<a
href="81a9c0ebc3"><code
class="notranslate">81a9c0e</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="093fb3d402"><code
class="notranslate">093fb3d</code></a> docs: replace <code
class="notranslate">var</code> with <code class="notranslate">let</code>
and <code class="notranslate">const</code> in rule examples (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19365">#19365</a>)
(Tanuj Kanti)</li>
<li>
<a
href="417de32985"><code
class="notranslate">417de32</code></a> docs: replace var with const in
rule examples (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19352">#19352</a>)
(jj)</li>
<li>
<a
href="17f2aaec16"><code
class="notranslate">17f2aae</code></a> docs: update getting-started
config to match default generated config (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19308">#19308</a>)
(0xDev)</li>
<li>
<a
href="8a0a5a8851"><code
class="notranslate">8a0a5a8</code></a> docs: better <code
class="notranslate">global ignores</code> instruction (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19297">#19297</a>)
(Jacopo Marrone)</li>
<li>
<a
href="6671a2cd8c"><code
class="notranslate">6671a2c</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="e39d3f22ff"><code
class="notranslate">e39d3f2</code></a> docs: fix divider for rule
category (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19264">#19264</a>)
(Tanuj Kanti)</li>
<li>
<a
href="e0cf53f80a"><code
class="notranslate">e0cf53f</code></a> docs: fix search result box
position for small screens (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19328">#19328</a>)
(Tanuj Kanti)</li>
<li>
<a
href="f92a6803a1"><code
class="notranslate">f92a680</code></a> docs: replace var with let or
const in rule examples (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19331">#19331</a>)
(Ravi Teja Kolla)</li>
<li>
<a
href="b04b84bc17"><code
class="notranslate">b04b84b</code></a> docs: revert accidental changes
in TS config files docs (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19336">#19336</a>)
(Francesco Trotta)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="9b9cb05848"><code
class="notranslate">9b9cb05</code></a> chore: upgrade @eslint/js@9.19.0
(<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19371">#19371</a>)
(Milos Djermanovic)</li>
<li>
<a
href="58560e70bb"><code
class="notranslate">58560e7</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="2089707091"><code
class="notranslate">2089707</code></a> test: fix failing test in Node.js
v22.13.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19345">#19345</a>)
(Francesco Trotta)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="1c87b41531...208e0b199f">See
the full diff on Github</a>. The new version differs by 21 commits:</p>
<ul>
<li><a
href="208e0b199f"><code>9.19.0</code></a></li>
<li><a
href="196dfdace6"><code>Build:
changelog update for 9.19.0</code></a></li>
<li><a
href="9b9cb05848"><code>chore:
upgrade @eslint/js@9.19.0 (#19371)</code></a></li>
<li><a
href="58560e70bb"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="cfea9abe0e"><code>docs:
Clarify overrideConfig option (#19370)</code></a></li>
<li><a
href="2b84f666cd"><code>docs:
Update README (#19362)</code></a></li>
<li><a
href="044f93cbbe"><code>docs:
clarify frozen rule description (#19351)</code></a></li>
<li><a
href="797ee7c0d6"><code>docs:
fix Bluesky links (#19368)</code></a></li>
<li><a
href="81a9c0ebc3"><code>docs:
Update README</code></a></li>
<li><a
href="093fb3d402"><code>docs:
replace `var` with `let` and `const` in rule examples
(#19365)</code></a></li>
<li><a
href="417de32985"><code>docs:
replace var with const in rule examples (#19352)</code></a></li>
<li><a
href="17f2aaec16"><code>docs:
update getting-started config to match default generated config
(#19308)</code></a></li>
<li><a
href="aae67172ab"><code>fix:
sync rule type header comments automatically (#19276)</code></a></li>
<li><a
href="8a0a5a8851"><code>docs:
better `global ignores` instruction (#19297)</code></a></li>
<li><a
href="2089707091"><code>test:
fix failing test in Node.js v22.13.0 (#19345)</code></a></li>
<li><a
href="6671a2cd8c"><code>docs:
Update README</code></a></li>
<li><a
href="1637b8e87d"><code>feat:
add `--report-unused-inline-configs` (#19201)</code></a></li>
<li><a
href="e39d3f22ff"><code>docs:
fix divider for rule category (#19264)</code></a></li>
<li><a
href="e0cf53f80a"><code>docs:
fix search result box position for small screens
(#19328)</code></a></li>
<li><a
href="f92a6803a1"><code>docs:
replace var with let or const in rule examples (#19331)</code></a></li>
<li><a
href="b04b84bc17"><code>docs:
revert accidental changes in TS config files docs
(#19336)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-02-10 12:10:32 +01:00
Mat Coke
aaf66b014f
Fix mismatched comment numbers in styles (#16380)
Corrected misplaced comment references to align with the listed
explanation above.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-02-10 09:55:07 +00:00
Robin Malfait
a659159bf1
Bump and pin prettier (#16382)
This PR bumps the Prettier dependencies, and also pins the version.

Noticed that a PR with a single empty commit started failing at the time
of writing this
(https://github.com/tailwindlabs/tailwindcss/pull/16306). This is
because prettier released a new minor version which results in slightly
different output.

Let's bump prettier and handle the differences, but also pin the version
to avoid this in the future.
2025-02-10 10:54:45 +01:00
Adam Wathan
ad001199f6
Prepare v4.0.5 (#16373)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create an issue to first
discuss any significant new features. This includes things like adding
new utilities, creating new at-rules, or adding new component examples
to the documentation.


https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md

-->

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2025-02-08 13:48:34 -05:00
Robin Malfait
d684733d80
Only expose used CSS variables (#16211)
This PR only exposes used CSS variables. 

My initial approach was to track the used variables, this was a bit
messy because it meant that we had to walk part of the AST(s) in
multiple places. We also had to be careful because sometimes if a
variable exists in an AST, that doesn't mean that it's actually used.
E.g.:

```css
h1 {
  color: var(--color-red-500); /* Definitely used, so let's keep it */
}

@utility foo {
  color: var(--color-blue-500); /* Hmm, used? */
}
```
In this last case, the `--color-blue-500` is part of the CSS AST, but as
long as `foo` the utility is not used, it won't end up in your actual
CSS file, therefore the variable is **not** used.

Alternatively, if the `foo` utility is used with an invalid variant
(e.g.: `group-[>.foo]:foo`, then the `@utility foo` code will still run
internally because variants are applied on top of the utility. This
means that it looks like `var(--color-blue-500)` is being used.

Another annoying side effect was that because variables are
conditionally generated, that the `@theme` -> `:root, :host` conversion
had to happen for every build, instead of once in the `compile(…)` step.

---

To prevent all the messy rules and additional booking while walking of
ASTs I thought about a different approach. We are only interested in
variables that are actually used. The only way we know for sure, is
right before the `toCss(…)` step. Any step before that could still throw
away AST nodes.

However, we do have an `optimizeAst` step right before printing to
simplify and optimize the AST. So the idea was to keep all the CSS
variables in the AST, and only in the `optimizeAst` step we perform a
kind of mark-and-sweep algorithm where we can first check which
variables are _actually_ used (these are the ones that are left in the
AST), and later we removed the ones that weren't part of known used
list.

Moving the logic to this step feels a natural spot for this to happen,
because we are in fact optimizing the AST. We were already walking the
AST, so we can just handle these cases while we are walking without
additional walks. Last but not least, this also means that there is only
a single spot where need to track and remove variables.

Now, there is a different part to this story. If you use a variable in
JS land for example, we also want to make sure that we keep the CSS
variable in the CSS. To do this, we can mark variables as being used in
the internal `Theme`.

The Oxide scanner will also emit used variables that it can find such as
`var(--color-red-500)` and will emit `--color-red-500` as a "candidate".
We can then proactively mark this one as used even though it may not be
used anyway in the actual AST.

---

### Always including all variables

Some users might make heavy use of JavaScript and string interpolation
where they _need_ all the variables to be present. Similar to the
`inline` and `reference` theme options, this also exposes a new `static`
option. This ensures that all the CSS variables will always be generated
regardless of whether it's used or not.

One handy feature is that you have granular control over this:
```css
/* These will always be generated */
@theme static {
  --color-primary: red;
  --color-secondary: blue;
}

/* Only generated when used */
@theme {
  --color-maybe: pink;
}
```

### Performance considerations:

Now that we are tracking which variables are being used, it means that
we will produce a smaller CSS file, but we are also doing more work (the
mark-and-sweep part). That said, ran some benchmarks and the changes
look like this:

Running it on Catalyst:
<img width="1086" alt="image"
src="https://github.com/user-attachments/assets/ec2124f0-2e64-4a11-aa5e-5f7ae6605962"
/>
_(probably within margin of error)_

Running it on Tailwind UI:
<img width="1113" alt="image"
src="https://github.com/user-attachments/assets/6bea2328-d790-4f33-a0ae-72654c688edb"
/>

### Test plan

- Tests have been updated with the removed CSS variables
- Added a dedicated integration test to show that Oxide can find
variables and mark them as used (so they are included)
- Ran the code on Catalyst, and verified that all the removed variables
are in fact not used anywhere in the codebase.

The diff on Catalyst looks like this:

<details>

```diff
diff --git a/templates/catalyst/out.css b/templates/catalyst/out.css
index f2b364ea..240d1d90 100644
--- a/templates/catalyst/out.css
+++ b/templates/catalyst/out.css
@@ -29,218 +29,111 @@
 @layer theme {
   :root, :host {
     --font-sans: Inter, sans-serif;
-    --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
     --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
       "Liberation Mono", "Courier New", monospace;
-    --color-red-50: oklch(0.971 0.013 17.38);
-    --color-red-100: oklch(0.936 0.032 17.717);
     --color-red-200: oklch(0.885 0.062 18.334);
     --color-red-300: oklch(0.808 0.114 19.571);
     --color-red-400: oklch(0.704 0.191 22.216);
     --color-red-500: oklch(0.637 0.237 25.331);
     --color-red-600: oklch(0.577 0.245 27.325);
     --color-red-700: oklch(0.505 0.213 27.518);
-    --color-red-800: oklch(0.444 0.177 26.899);
     --color-red-900: oklch(0.396 0.141 25.723);
-    --color-red-950: oklch(0.258 0.092 26.042);
-    --color-orange-50: oklch(0.98 0.016 73.684);
-    --color-orange-100: oklch(0.954 0.038 75.164);
     --color-orange-200: oklch(0.901 0.076 70.697);
     --color-orange-300: oklch(0.837 0.128 66.29);
     --color-orange-400: oklch(0.75 0.183 55.934);
     --color-orange-500: oklch(0.705 0.213 47.604);
     --color-orange-600: oklch(0.646 0.222 41.116);
     --color-orange-700: oklch(0.553 0.195 38.402);
-    --color-orange-800: oklch(0.47 0.157 37.304);
     --color-orange-900: oklch(0.408 0.123 38.172);
-    --color-orange-950: oklch(0.266 0.079 36.259);
-    --color-amber-50: oklch(0.987 0.022 95.277);
-    --color-amber-100: oklch(0.962 0.059 95.617);
-    --color-amber-200: oklch(0.924 0.12 95.746);
-    --color-amber-300: oklch(0.879 0.169 91.605);
     --color-amber-400: oklch(0.828 0.189 84.429);
     --color-amber-500: oklch(0.769 0.188 70.08);
     --color-amber-600: oklch(0.666 0.179 58.318);
     --color-amber-700: oklch(0.555 0.163 48.998);
-    --color-amber-800: oklch(0.473 0.137 46.201);
-    --color-amber-900: oklch(0.414 0.112 45.904);
     --color-amber-950: oklch(0.279 0.077 45.635);
-    --color-yellow-50: oklch(0.987 0.026 102.212);
-    --color-yellow-100: oklch(0.973 0.071 103.193);
-    --color-yellow-200: oklch(0.945 0.129 101.54);
     --color-yellow-300: oklch(0.905 0.182 98.111);
     --color-yellow-400: oklch(0.852 0.199 91.936);
-    --color-yellow-500: oklch(0.795 0.184 86.047);
     --color-yellow-600: oklch(0.681 0.162 75.834);
     --color-yellow-700: oklch(0.554 0.135 66.442);
-    --color-yellow-800: oklch(0.476 0.114 61.907);
-    --color-yellow-900: oklch(0.421 0.095 57.708);
     --color-yellow-950: oklch(0.286 0.066 53.813);
-    --color-lime-50: oklch(0.986 0.031 120.757);
-    --color-lime-100: oklch(0.967 0.067 122.328);
-    --color-lime-200: oklch(0.938 0.127 124.321);
     --color-lime-300: oklch(0.897 0.196 126.665);
     --color-lime-400: oklch(0.841 0.238 128.85);
-    --color-lime-500: oklch(0.768 0.233 130.85);
     --color-lime-600: oklch(0.648 0.2 131.684);
     --color-lime-700: oklch(0.532 0.157 131.589);
-    --color-lime-800: oklch(0.453 0.124 130.933);
-    --color-lime-900: oklch(0.405 0.101 131.063);
     --color-lime-950: oklch(0.274 0.072 132.109);
-    --color-green-50: oklch(0.982 0.018 155.826);
-    --color-green-100: oklch(0.962 0.044 156.743);
-    --color-green-200: oklch(0.925 0.084 155.995);
-    --color-green-300: oklch(0.871 0.15 154.449);
     --color-green-400: oklch(0.792 0.209 151.711);
     --color-green-500: oklch(0.723 0.219 149.579);
     --color-green-600: oklch(0.627 0.194 149.214);
     --color-green-700: oklch(0.527 0.154 150.069);
-    --color-green-800: oklch(0.448 0.119 151.328);
     --color-green-900: oklch(0.393 0.095 152.535);
-    --color-green-950: oklch(0.266 0.065 152.934);
-    --color-emerald-50: oklch(0.979 0.021 166.113);
-    --color-emerald-100: oklch(0.95 0.052 163.051);
-    --color-emerald-200: oklch(0.905 0.093 164.15);
-    --color-emerald-300: oklch(0.845 0.143 164.978);
     --color-emerald-400: oklch(0.765 0.177 163.223);
     --color-emerald-500: oklch(0.696 0.17 162.48);
     --color-emerald-600: oklch(0.596 0.145 163.225);
     --color-emerald-700: oklch(0.508 0.118 165.612);
-    --color-emerald-800: oklch(0.432 0.095 166.913);
     --color-emerald-900: oklch(0.378 0.077 168.94);
-    --color-emerald-950: oklch(0.262 0.051 172.552);
-    --color-teal-50: oklch(0.984 0.014 180.72);
-    --color-teal-100: oklch(0.953 0.051 180.801);
-    --color-teal-200: oklch(0.91 0.096 180.426);
     --color-teal-300: oklch(0.855 0.138 181.071);
     --color-teal-400: oklch(0.777 0.152 181.912);
     --color-teal-500: oklch(0.704 0.14 182.503);
     --color-teal-600: oklch(0.6 0.118 184.704);
     --color-teal-700: oklch(0.511 0.096 186.391);
-    --color-teal-800: oklch(0.437 0.078 188.216);
     --color-teal-900: oklch(0.386 0.063 188.416);
-    --color-teal-950: oklch(0.277 0.046 192.524);
-    --color-cyan-50: oklch(0.984 0.019 200.873);
-    --color-cyan-100: oklch(0.956 0.045 203.388);
-    --color-cyan-200: oklch(0.917 0.08 205.041);
     --color-cyan-300: oklch(0.865 0.127 207.078);
     --color-cyan-400: oklch(0.789 0.154 211.53);
     --color-cyan-500: oklch(0.715 0.143 215.221);
-    --color-cyan-600: oklch(0.609 0.126 221.723);
     --color-cyan-700: oklch(0.52 0.105 223.128);
-    --color-cyan-800: oklch(0.45 0.085 224.283);
-    --color-cyan-900: oklch(0.398 0.07 227.392);
     --color-cyan-950: oklch(0.302 0.056 229.695);
-    --color-sky-50: oklch(0.977 0.013 236.62);
-    --color-sky-100: oklch(0.951 0.026 236.824);
-    --color-sky-200: oklch(0.901 0.058 230.902);
     --color-sky-300: oklch(0.828 0.111 230.318);
-    --color-sky-400: oklch(0.746 0.16 232.661);
     --color-sky-500: oklch(0.685 0.169 237.323);
     --color-sky-600: oklch(0.588 0.158 241.966);
     --color-sky-700: oklch(0.5 0.134 242.749);
-    --color-sky-800: oklch(0.443 0.11 240.79);
     --color-sky-900: oklch(0.391 0.09 240.876);
-    --color-sky-950: oklch(0.293 0.066 243.157);
-    --color-blue-50: oklch(0.97 0.014 254.604);
-    --color-blue-100: oklch(0.932 0.032 255.585);
-    --color-blue-200: oklch(0.882 0.059 254.128);
     --color-blue-300: oklch(0.809 0.105 251.813);
     --color-blue-400: oklch(0.707 0.165 254.624);
     --color-blue-500: oklch(0.623 0.214 259.815);
     --color-blue-600: oklch(0.546 0.245 262.881);
     --color-blue-700: oklch(0.488 0.243 264.376);
-    --color-blue-800: oklch(0.424 0.199 265.638);
     --color-blue-900: oklch(0.379 0.146 265.522);
-    --color-blue-950: oklch(0.282 0.091 267.935);
-    --color-indigo-50: oklch(0.962 0.018 272.314);
-    --color-indigo-100: oklch(0.93 0.034 272.788);
     --color-indigo-200: oklch(0.87 0.065 274.039);
     --color-indigo-300: oklch(0.785 0.115 274.713);
     --color-indigo-400: oklch(0.673 0.182 276.935);
     --color-indigo-500: oklch(0.585 0.233 277.117);
     --color-indigo-600: oklch(0.511 0.262 276.966);
     --color-indigo-700: oklch(0.457 0.24 277.023);
-    --color-indigo-800: oklch(0.398 0.195 277.366);
     --color-indigo-900: oklch(0.359 0.144 278.697);
-    --color-indigo-950: oklch(0.257 0.09 281.288);
-    --color-violet-50: oklch(0.969 0.016 293.756);
-    --color-violet-100: oklch(0.943 0.029 294.588);
     --color-violet-200: oklch(0.894 0.057 293.283);
     --color-violet-300: oklch(0.811 0.111 293.571);
     --color-violet-400: oklch(0.702 0.183 293.541);
     --color-violet-500: oklch(0.606 0.25 292.717);
     --color-violet-600: oklch(0.541 0.281 293.009);
     --color-violet-700: oklch(0.491 0.27 292.581);
-    --color-violet-800: oklch(0.432 0.232 292.759);
     --color-violet-900: oklch(0.38 0.189 293.745);
-    --color-violet-950: oklch(0.283 0.141 291.089);
-    --color-purple-50: oklch(0.977 0.014 308.299);
-    --color-purple-100: oklch(0.946 0.033 307.174);
     --color-purple-200: oklch(0.902 0.063 306.703);
     --color-purple-300: oklch(0.827 0.119 306.383);
     --color-purple-400: oklch(0.714 0.203 305.504);
     --color-purple-500: oklch(0.627 0.265 303.9);
     --color-purple-600: oklch(0.558 0.288 302.321);
     --color-purple-700: oklch(0.496 0.265 301.924);
-    --color-purple-800: oklch(0.438 0.218 303.724);
     --color-purple-900: oklch(0.381 0.176 304.987);
-    --color-purple-950: oklch(0.291 0.149 302.717);
-    --color-fuchsia-50: oklch(0.977 0.017 320.058);
-    --color-fuchsia-100: oklch(0.952 0.037 318.852);
     --color-fuchsia-200: oklch(0.903 0.076 319.62);
     --color-fuchsia-300: oklch(0.833 0.145 321.434);
     --color-fuchsia-400: oklch(0.74 0.238 322.16);
     --color-fuchsia-500: oklch(0.667 0.295 322.15);
     --color-fuchsia-600: oklch(0.591 0.293 322.896);
     --color-fuchsia-700: oklch(0.518 0.253 323.949);
-    --color-fuchsia-800: oklch(0.452 0.211 324.591);
     --color-fuchsia-900: oklch(0.401 0.17 325.612);
-    --color-fuchsia-950: oklch(0.293 0.136 325.661);
-    --color-pink-50: oklch(0.971 0.014 343.198);
-    --color-pink-100: oklch(0.948 0.028 342.258);
     --color-pink-200: oklch(0.899 0.061 343.231);
     --color-pink-300: oklch(0.823 0.12 346.018);
     --color-pink-400: oklch(0.718 0.202 349.761);
     --color-pink-500: oklch(0.656 0.241 354.308);
     --color-pink-600: oklch(0.592 0.249 0.584);
     --color-pink-700: oklch(0.525 0.223 3.958);
-    --color-pink-800: oklch(0.459 0.187 3.815);
     --color-pink-900: oklch(0.408 0.153 2.432);
-    --color-pink-950: oklch(0.284 0.109 3.907);
-    --color-rose-50: oklch(0.969 0.015 12.422);
-    --color-rose-100: oklch(0.941 0.03 12.58);
     --color-rose-200: oklch(0.892 0.058 10.001);
     --color-rose-300: oklch(0.81 0.117 11.638);
     --color-rose-400: oklch(0.712 0.194 13.428);
     --color-rose-500: oklch(0.645 0.246 16.439);
     --color-rose-600: oklch(0.586 0.253 17.585);
     --color-rose-700: oklch(0.514 0.222 16.935);
-    --color-rose-800: oklch(0.455 0.188 13.697);
     --color-rose-900: oklch(0.41 0.159 10.272);
-    --color-rose-950: oklch(0.271 0.105 12.094);
-    --color-slate-50: oklch(0.984 0.003 247.858);
-    --color-slate-100: oklch(0.968 0.007 247.896);
-    --color-slate-200: oklch(0.929 0.013 255.508);
-    --color-slate-300: oklch(0.869 0.022 252.894);
-    --color-slate-400: oklch(0.704 0.04 256.788);
-    --color-slate-500: oklch(0.554 0.046 257.417);
-    --color-slate-600: oklch(0.446 0.043 257.281);
-    --color-slate-700: oklch(0.372 0.044 257.287);
-    --color-slate-800: oklch(0.279 0.041 260.031);
-    --color-slate-900: oklch(0.208 0.042 265.755);
-    --color-slate-950: oklch(0.129 0.042 264.695);
-    --color-gray-50: oklch(0.985 0.002 247.839);
-    --color-gray-100: oklch(0.967 0.003 264.542);
-    --color-gray-200: oklch(0.928 0.006 264.531);
-    --color-gray-300: oklch(0.872 0.01 258.338);
-    --color-gray-400: oklch(0.707 0.022 261.325);
-    --color-gray-500: oklch(0.551 0.027 264.364);
-    --color-gray-600: oklch(0.446 0.03 256.802);
-    --color-gray-700: oklch(0.373 0.034 259.733);
-    --color-gray-800: oklch(0.278 0.033 256.848);
-    --color-gray-900: oklch(0.21 0.034 264.665);
-    --color-gray-950: oklch(0.13 0.028 261.692);
     --color-zinc-50: oklch(0.985 0 0);
     --color-zinc-100: oklch(0.967 0.001 286.375);
     --color-zinc-200: oklch(0.92 0.004 286.32);
@@ -252,38 +145,9 @@
     --color-zinc-800: oklch(0.274 0.006 286.033);
     --color-zinc-900: oklch(0.21 0.006 285.885);
     --color-zinc-950: oklch(0.141 0.005 285.823);
-    --color-neutral-50: oklch(0.985 0 0);
-    --color-neutral-100: oklch(0.97 0 0);
-    --color-neutral-200: oklch(0.922 0 0);
-    --color-neutral-300: oklch(0.87 0 0);
-    --color-neutral-400: oklch(0.708 0 0);
-    --color-neutral-500: oklch(0.556 0 0);
-    --color-neutral-600: oklch(0.439 0 0);
-    --color-neutral-700: oklch(0.371 0 0);
-    --color-neutral-800: oklch(0.269 0 0);
-    --color-neutral-900: oklch(0.205 0 0);
-    --color-neutral-950: oklch(0.145 0 0);
-    --color-stone-50: oklch(0.985 0.001 106.423);
-    --color-stone-100: oklch(0.97 0.001 106.424);
-    --color-stone-200: oklch(0.923 0.003 48.717);
-    --color-stone-300: oklch(0.869 0.005 56.366);
-    --color-stone-400: oklch(0.709 0.01 56.259);
-    --color-stone-500: oklch(0.553 0.013 58.071);
-    --color-stone-600: oklch(0.444 0.011 73.639);
-    --color-stone-700: oklch(0.374 0.01 67.558);
-    --color-stone-800: oklch(0.268 0.007 34.298);
-    --color-stone-900: oklch(0.216 0.006 56.043);
-    --color-stone-950: oklch(0.147 0.004 49.25);
     --color-black: #000;
     --color-white: #fff;
     --spacing: 0.25rem;
-    --breakpoint-sm: 40rem;
-    --breakpoint-md: 48rem;
-    --breakpoint-lg: 64rem;
-    --breakpoint-xl: 80rem;
-    --breakpoint-2xl: 96rem;
-    --container-3xs: 16rem;
-    --container-2xs: 18rem;
     --container-xs: 20rem;
     --container-sm: 24rem;
     --container-md: 28rem;
@@ -302,92 +166,23 @@
     --text-base: 1rem;
     --text-base--line-height: calc(1.5 / 1);
     --text-lg: 1.125rem;
-    --text-lg--line-height: calc(1.75 / 1.125);
     --text-xl: 1.25rem;
-    --text-xl--line-height: calc(1.75 / 1.25);
     --text-2xl: 1.5rem;
-    --text-2xl--line-height: calc(2 / 1.5);
-    --text-3xl: 1.875rem;
-    --text-3xl--line-height: calc(2.25 / 1.875);
-    --text-4xl: 2.25rem;
-    --text-4xl--line-height: calc(2.5 / 2.25);
-    --text-5xl: 3rem;
-    --text-5xl--line-height: 1;
-    --text-6xl: 3.75rem;
-    --text-6xl--line-height: 1;
-    --text-7xl: 4.5rem;
-    --text-7xl--line-height: 1;
-    --text-8xl: 6rem;
-    --text-8xl--line-height: 1;
-    --text-9xl: 8rem;
-    --text-9xl--line-height: 1;
-    --font-weight-thin: 100;
-    --font-weight-extralight: 200;
-    --font-weight-light: 300;
     --font-weight-normal: 400;
     --font-weight-medium: 500;
     --font-weight-semibold: 600;
     --font-weight-bold: 700;
-    --font-weight-extrabold: 800;
-    --font-weight-black: 900;
-    --tracking-tighter: -0.05em;
-    --tracking-tight: -0.025em;
-    --tracking-normal: 0em;
-    --tracking-wide: 0.025em;
-    --tracking-wider: 0.05em;
-    --tracking-widest: 0.1em;
-    --leading-tight: 1.25;
-    --leading-snug: 1.375;
-    --leading-normal: 1.5;
-    --leading-relaxed: 1.625;
-    --leading-loose: 2;
-    --radius-xs: 0.125rem;
     --radius-sm: 0.25rem;
     --radius-md: 0.375rem;
     --radius-lg: 0.5rem;
     --radius-xl: 0.75rem;
     --radius-2xl: 1rem;
     --radius-3xl: 1.5rem;
-    --radius-4xl: 2rem;
-    --shadow-2xs: 0 1px rgb(0 0 0 / 0.05);
-    --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
-    --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
-    --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1),
-      0 2px 4px -2px rgb(0 0 0 / 0.1);
-    --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1),
-      0 4px 6px -4px rgb(0 0 0 / 0.1);
-    --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1),
-      0 8px 10px -6px rgb(0 0 0 / 0.1);
-    --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
-    --inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05);
-    --inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05);
-    --inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05);
-    --drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 0.05);
-    --drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.15);
-    --drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12);
-    --drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15);
-    --drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1);
-    --drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15);
     --ease-in: cubic-bezier(0.4, 0, 1, 1);
     --ease-out: cubic-bezier(0, 0, 0.2, 1);
     --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
-    --animate-spin: spin 1s linear infinite;
-    --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
-    --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
-    --animate-bounce: bounce 1s infinite;
-    --blur-xs: 4px;
-    --blur-sm: 8px;
     --blur-md: 12px;
-    --blur-lg: 16px;
     --blur-xl: 24px;
-    --blur-2xl: 40px;
-    --blur-3xl: 64px;
-    --perspective-dramatic: 100px;
-    --perspective-near: 300px;
-    --perspective-normal: 500px;
-    --perspective-midrange: 800px;
-    --perspective-distant: 1200px;
-    --aspect-video: 16 / 9;
     --default-transition-duration: 150ms;
     --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
     --default-font-family: var(--font-sans);
```

</details>

If you have `ripgrep` installed, you can use this command to verify that
these variables are indeed not used anywhere:

<details>

```shell
rg "\-\-font-serif\b"
rg "\-\-color-red-50\b"
rg "\-\-color-red-100\b"
rg "\-\-color-red-800\b"
rg "\-\-color-red-950\b"
rg "\-\-color-orange-50\b"
rg "\-\-color-orange-100\b"
rg "\-\-color-orange-800\b"
rg "\-\-color-orange-950\b"
rg "\-\-color-amber-50\b"
rg "\-\-color-amber-100\b"
rg "\-\-color-amber-200\b"
rg "\-\-color-amber-300\b"
rg "\-\-color-amber-800\b"
rg "\-\-color-amber-900\b"
rg "\-\-color-yellow-50\b"
rg "\-\-color-yellow-100\b"
rg "\-\-color-yellow-200\b"
rg "\-\-color-yellow-500\b"
rg "\-\-color-yellow-800\b"
rg "\-\-color-yellow-900\b"
rg "\-\-color-lime-50\b"
rg "\-\-color-lime-100\b"
rg "\-\-color-lime-200\b"
rg "\-\-color-lime-500\b"
rg "\-\-color-lime-800\b"
rg "\-\-color-lime-900\b"
rg "\-\-color-green-50\b"
rg "\-\-color-green-100\b"
rg "\-\-color-green-200\b"
rg "\-\-color-green-300\b"
rg "\-\-color-green-800\b"
rg "\-\-color-green-950\b"
rg "\-\-color-emerald-50\b"
rg "\-\-color-emerald-100\b"
rg "\-\-color-emerald-200\b"
rg "\-\-color-emerald-300\b"
rg "\-\-color-emerald-800\b"
rg "\-\-color-emerald-950\b"
rg "\-\-color-teal-50\b"
rg "\-\-color-teal-100\b"
rg "\-\-color-teal-200\b"
rg "\-\-color-teal-800\b"
rg "\-\-color-teal-950\b"
rg "\-\-color-cyan-50\b"
rg "\-\-color-cyan-100\b"
rg "\-\-color-cyan-200\b"
rg "\-\-color-cyan-600\b"
rg "\-\-color-cyan-800\b"
rg "\-\-color-cyan-900\b"
rg "\-\-color-sky-50\b"
rg "\-\-color-sky-100\b"
rg "\-\-color-sky-200\b"
rg "\-\-color-sky-400\b"
rg "\-\-color-sky-800\b"
rg "\-\-color-sky-950\b"
rg "\-\-color-blue-50\b"
rg "\-\-color-blue-100\b"
rg "\-\-color-blue-200\b"
rg "\-\-color-blue-800\b"
rg "\-\-color-blue-950\b"
rg "\-\-color-indigo-50\b"
rg "\-\-color-indigo-100\b"
rg "\-\-color-indigo-800\b"
rg "\-\-color-indigo-950\b"
rg "\-\-color-violet-50\b"
rg "\-\-color-violet-100\b"
rg "\-\-color-violet-800\b"
rg "\-\-color-violet-950\b"
rg "\-\-color-purple-50\b"
rg "\-\-color-purple-100\b"
rg "\-\-color-purple-800\b"
rg "\-\-color-purple-950\b"
rg "\-\-color-fuchsia-50\b"
rg "\-\-color-fuchsia-100\b"
rg "\-\-color-fuchsia-800\b"
rg "\-\-color-fuchsia-950\b"
rg "\-\-color-pink-50\b"
rg "\-\-color-pink-100\b"
rg "\-\-color-pink-800\b"
rg "\-\-color-pink-950\b"
rg "\-\-color-rose-50\b"
rg "\-\-color-rose-100\b"
rg "\-\-color-rose-800\b"
rg "\-\-color-rose-950\b"
rg "\-\-color-slate-50\b"
rg "\-\-color-slate-100\b"
rg "\-\-color-slate-200\b"
rg "\-\-color-slate-300\b"
rg "\-\-color-slate-400\b"
rg "\-\-color-slate-500\b"
rg "\-\-color-slate-600\b"
rg "\-\-color-slate-700\b"
rg "\-\-color-slate-800\b"
rg "\-\-color-slate-900\b"
rg "\-\-color-slate-950\b"
rg "\-\-color-gray-50\b"
rg "\-\-color-gray-100\b"
rg "\-\-color-gray-200\b"
rg "\-\-color-gray-300\b"
rg "\-\-color-gray-400\b"
rg "\-\-color-gray-500\b"
rg "\-\-color-gray-600\b"
rg "\-\-color-gray-700\b"
rg "\-\-color-gray-800\b"
rg "\-\-color-gray-900\b"
rg "\-\-color-gray-950\b"
rg "\-\-color-neutral-50\b"
rg "\-\-color-neutral-100\b"
rg "\-\-color-neutral-200\b"
rg "\-\-color-neutral-300\b"
rg "\-\-color-neutral-400\b"
rg "\-\-color-neutral-500\b"
rg "\-\-color-neutral-600\b"
rg "\-\-color-neutral-700\b"
rg "\-\-color-neutral-800\b"
rg "\-\-color-neutral-900\b"
rg "\-\-color-neutral-950\b"
rg "\-\-color-stone-50\b"
rg "\-\-color-stone-100\b"
rg "\-\-color-stone-200\b"
rg "\-\-color-stone-300\b"
rg "\-\-color-stone-400\b"
rg "\-\-color-stone-500\b"
rg "\-\-color-stone-600\b"
rg "\-\-color-stone-700\b"
rg "\-\-color-stone-800\b"
rg "\-\-color-stone-900\b"
rg "\-\-color-stone-950\b"
rg "\-\-breakpoint-sm\b"
rg "\-\-breakpoint-md\b"
rg "\-\-breakpoint-lg\b"
rg "\-\-breakpoint-xl\b"
rg "\-\-breakpoint-2xl\b"
rg "\-\-container-3xs\b"
rg "\-\-container-2xs\b"
rg "\-\-text-lg--line-height\b"
rg "\-\-text-xl--line-height\b"
rg "\-\-text-2xl--line-height\b"
rg "\-\-text-3xl\b"
rg "\-\-text-3xl--line-height\b"
rg "\-\-text-4xl\b"
rg "\-\-text-4xl--line-height\b"
rg "\-\-text-5xl\b"
rg "\-\-text-5xl--line-height\b"
rg "\-\-text-6xl\b"
rg "\-\-text-6xl--line-height\b"
rg "\-\-text-7xl\b"
rg "\-\-text-7xl--line-height\b"
rg "\-\-text-8xl\b"
rg "\-\-text-8xl--line-height\b"
rg "\-\-text-9xl\b"
rg "\-\-text-9xl--line-height\b"
rg "\-\-font-weight-thin\b"
rg "\-\-font-weight-extralight\b"
rg "\-\-font-weight-light\b"
rg "\-\-font-weight-extrabold\b"
rg "\-\-font-weight-black\b"
rg "\-\-tracking-tighter\b"
rg "\-\-tracking-tight\b"
rg "\-\-tracking-normal\b"
rg "\-\-tracking-wide\b"
rg "\-\-tracking-wider\b"
rg "\-\-tracking-widest\b"
rg "\-\-leading-tight\b"
rg "\-\-leading-snug\b"
rg "\-\-leading-normal\b"
rg "\-\-leading-relaxed\b"
rg "\-\-leading-loose\b"
rg "\-\-radius-xs\b"
rg "\-\-radius-4xl\b"
rg "\-\-shadow-2xs\b"
rg "\-\-shadow-xs\b"
rg "\-\-shadow-sm\b"
rg "\-\-shadow-md\b"
rg "\-\-shadow-lg\b"
rg "\-\-shadow-xl\b"
rg "\-\-shadow-2xl\b"
rg "\-\-inset-shadow-2xs\b"
rg "\-\-inset-shadow-xs\b"
rg "\-\-inset-shadow-sm\b"
rg "\-\-drop-shadow-xs\b"
rg "\-\-drop-shadow-sm\b"
rg "\-\-drop-shadow-md\b"
rg "\-\-drop-shadow-lg\b"
rg "\-\-drop-shadow-xl\b"
rg "\-\-drop-shadow-2xl\b"
rg "\-\-animate-spin\b"
rg "\-\-animate-ping\b"
rg "\-\-animate-pulse\b"
rg "\-\-animate-bounce\b"
rg "\-\-blur-xs\b"
rg "\-\-blur-sm\b"
rg "\-\-blur-lg\b"
rg "\-\-blur-2xl\b"
rg "\-\-blur-3xl\b"
rg "\-\-perspective-dramatic\b"
rg "\-\-perspective-near\b"
rg "\-\-perspective-normal\b"
rg "\-\-perspective-midrange\b"
rg "\-\-perspective-distant\b"
rg "\-\-aspect-video\b"
```

</details>

The only exception I noticed is that we have this:
```css
src/typography.utilities.css
10:  @media (width >= theme(--breakpoint-sm)) {
```
But this is not a variable, but it's replaced at build time with the
actual value, so this is not a real issue.

Testing on other templates:

<img width="2968" alt="image"
src="https://github.com/user-attachments/assets/cabf121d-4cb9-468f-9cf5-ceb02609dc7d"
/>


Fixes: https://github.com/tailwindlabs/tailwindcss/issues/16145

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2025-02-07 17:12:47 +00:00
Robin Malfait
81de67c5ff
Cleanup Tailwind Play workflow (#16310)
This PR cleans up the Tailwind Play update by removing unnecessary
inputs because Tailwind Play will take care of this.
2025-02-06 18:56:29 +00:00
Martin SCHNEIDER
25b42789b0
Remove useless console.log in @tailwind/vite package (#16307) 2025-02-06 16:54:21 +00:00
Robin Malfait
83fdf373aa
Prepare v4.0.4 (#16302)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-02-06 15:18:11 +01:00
Philipp Spiess
144581d3e4
Properly skip over nodes when using replaceNode (#16300)
Fixes #16298 

This PR fixes an issue where using an AST walk in combination with
`replaceNode` and various `SkipAction` would either cause children to be
visited multiple times or not visited at all even though it should. This
PR fixes the issue which also means we can get rid of a custom walk for
`@variant` inside the `@media` that was used to apply `@variant` because
we never recursively visited children inside the `@media` rule.

Because we now can use the regular walk for `@variant`, we now properly
convert `@variant` to `@custom-variant` inside `@reference`-ed
stylesheet which also fixes #16298

## Test plan

Lots of tests added to ensure the combinations of `WalkAction` and
`replaceWith()` works as expected.
2025-02-06 14:30:04 +01:00
Philipp Spiess
1e949af9a1
Standalone: Use bun baseline builds for Linux (#16244)
Closes #15181

This PR changes the Standalone builds to use bun baseline instead. This
compiles to a reduced instruction set and should work around the
compatibility issues experienced across older server hardware.

## Test plan

See
https://github.com/tailwindlabs/tailwindcss/issues/15181#issuecomment-2634621266

Also tested it with the repor from @npezza93 and it now works locally as
well with a Docker build.
2025-02-06 14:29:54 +01:00
Robin Malfait
d566dbdef6
Improve performance of rebuilds (#16283)
This PR introduces a performance improvement we noticed while working on
on: https://github.com/tailwindlabs/tailwindcss/pull/16211

We noticed that `substituteFunctions` was being called on every `node`
after the `compileAstNodes` was done. However, the `compileAstNodes` is
heavily cached.

By moving the `substituteFunctions` we into the cached `compileAstNodes`
we sped up performance for Catalyst rebuilds from ~15ms to ~10ms.


| Before | After |
| --- | --- |
| <img width="710" alt="image"
src="https://github.com/user-attachments/assets/eaf110d9-2f88-447c-9b10-c77d47bd99a5"
/> | <img width="696" alt="image"
src="https://github.com/user-attachments/assets/c5a2ff4c-d75e-4e35-a2b6-d896598810f5"
/> |
2025-02-05 18:46:14 +01:00
Philipp Spiess
837e240b4d
Add Freebsd build target (#16277)
Closes #15731

This PR adds a FreeBSD build target to our CI workflows. It was tested
on CI:
3672361307

However, due to the build not emitting final npm packages, we don't have
a way to actually test the final package before we ship it to an
insiders release.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-02-05 15:48:05 +00:00
depfu[bot]
0ecc22b5cc
Update all of nextjs 15.1.4 → 15.1.6 (patch) (#16257) 2025-02-05 16:22:35 +01:00
Philipp Spiess
3f8e7647b6
Take NODE_PATH into account when resolving modules (#16274)
Fixes #15847

This PR changes the node specific resolver config to takes the eventual
`NODE_PATH` env into account.

## Test plan

See #15847

<img width="1273" alt="Screenshot 2025-02-05 at 12 58 20"
src="https://github.com/user-attachments/assets/8e1b99d1-608d-437a-a7b2-212feb153da5"
/>
2025-02-05 16:21:31 +01:00
Robin Malfait
82d486adb5
Fix order-first and order-last for Firefox (#16266)
This PR fixes an issue where `order-last` doesn't work as expected in
Firefox.

The implementation of `order-last`, looks like this: 
```css
.order-last {
  order: calc(infinity);
}
```

Which is valid CSS, and `calc(infinity)` is even valid in Firefox. You
can use this in other properties such as `border-radius`:
```css
.rounded-full {
  border-radius: calc(infinity * 1px);
}
```

While this works, in properties like `order` it just doesn't work.

Fixes: #16165
2025-02-05 12:04:43 +01:00
Philipp Spiess
3b61277e7a
Don't crash when setting JS theme value to null (#16210)
Closes #16035

In v3 it was possible to unset a specific color namespace by setting
doing something like this:

```js
export default {
  theme: {
    extend: {
      colors: {
        red: null,
      },
    },
  },
}
```

This pattern would crash in v4 right now due to the theme access
function not being able to work on the red property being a `null`. This
PR fixes this crash.

However it leaves the behavior as-is for now so that the red namespace
_defined via CSS will still be accessible_. This is technically
different from v3 but fixing this would be more work as we only allow
unsetting top-level namespaces in the interop layer (via the
non-`extend`-theme-object). I would recommend migrating to the v4 API
for doing these partial namespace resets if you want to get rid of the
defaults in v4:

```css
@theme {
  --color-red-*: initial;
}
```

## Test plan

The crash was mainly captured via the test in `compat/config.test.ts`
but I've added two more tests across the different levels of
abstractions so that it's clear what `null` should be doing.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-02-04 15:43:48 +01:00
Philipp Spiess
5601fb50a9
Upgrade @parcel/watcher to 2.5.1 (#16248)
Closes #16225
2025-02-04 15:14:52 +01:00
Philipp Spiess
9fd6766308
Ensure first argument to var(…) still unescapes \_ (#16206)
Resolves #16170

This PR fixes an issue where the previously opted-out escaping of the
first argument for the `var(…)` function was not unescaped at all. This
was introduced in https://github.com/tailwindlabs/tailwindcss/pull/14776
where the intention was to not require escaping of underscores in the
var function (e.g. `ml-[var(--spacing-1_5)]`). However, I do think it
still makes sense to unescape an eventually escaped underline for
consistency.

## Test plan

The example from #1670 now parses as expected:

<img width="904" alt="Screenshot 2025-02-03 at 13 51 35"
src="https://github.com/user-attachments/assets/cac0f06e-37da-4dcb-a554-9606d144a8d5"
/>

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-02-04 15:13:18 +01:00
Philipp Spiess
e1a85ac260
Vite: Skip parsing stylesheets with the ?commonjs-proxy flag (#16238)
Fixes #16233

Vite has a number of special parameters that can be appended to `.css`
files that make it actually load as a JavaScript module. One such
parameter that we haven't handled before is the `?commonjs-proxy` flag.
When importing e.g. `plotly.js/lib/core`, the dependency tree would
eventually load a file called `*.css?commonjs-proxy`. We previously
scanned this for candidates even though it was not, in-fact, a
stylesheet.

This PR fixes this by adding the `?commonjs-proxy` to the ignore list. I
have also updated `SPECIAL_QUERY_RE` to more closely match the Vite
implementation. It does seem like this was the only condition we were
missing, though:
2b2299cbac/packages/vite/src/node/plugins/css.ts (L511-L517)

## Test plan

Add and import `plotly.js/lib/core` into a Vite app. I also added an
integration test to do that.

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-02-04 12:45:23 +01:00
Robin Malfait
06dfa399e6
Ensure we always emit @keyframes correctly (#16237)
This PR fixes an issue where we didn't always generate the `@keyframes`.
Right now we only generate `@keyframes` _if_ they are being used as one
of the `--animate-*` utilities.

However, if your `--animate-*` definition is pretty long such that it is
defined across multiple lines, then we didn't always generate the
`@keyframes` for it.

This is because the animation name would look like
`'my-animation-name\n'` instead of `'my-animation-name'`.

Fixes: #16227
2025-02-04 11:38:31 +00:00
Vũ Văn Dũng
ac202ffd50
Update upgrade CLI to handle containers config correctly (#16169) 2025-02-03 15:16:14 +01:00
depfu[bot]
b8d8548a5e
Update tsup 8.2.4 → 8.3.6 (minor) (#16155) 2025-02-03 11:20:51 +01:00
Robin Malfait
b7c3f50143
Prepare v4.0.3 (#16146) 2025-02-01 12:38:55 +01:00
Justin Wong
b7436f8b59
Fix @import url() being stripped (#16144) 2025-02-01 12:16:16 +01:00
Robin Malfait
50bafce756
Prepare for v4.0.2 release (#16131)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-01-31 19:30:36 +01:00
Jordan Pittman
4052eb24bf
Allow @variant to be used at the top-level (#16129)
This makes it so `@variant` is replaced at the top level and not just
within rules. This also fixes a bug where `@variant` wasn't handled when
inside an `@media` at-rule.
2025-01-31 19:18:11 +01:00
Robin Malfait
7f1d0970c3
Do not emit empty rules/at-rules (#16121)
This PR is an optimization where it will not emit empty rules and
at-rules. I noticed this while working on
https://github.com/tailwindlabs/tailwindcss/pull/16120 where we emitted:
```css
:root, :host {
}
```

There are some exceptions for "empty" at-rules, such as:

```css
@charset "UTF-8";
@layer foo, bar, baz;
@custom-media --modern (color), (hover);
@namespace "http://www.w3.org/1999/xhtml";
```

These don't have a body, but they still have a purpose and therefore
they will be emitted.

However, if you look at this:

```css
/* Empty rule */
.foo {
}

/* Empty rule, with nesting */
.foo {
  .bar {
  }
  .baz {
  }
}

/* Empty rule, with special case '&' rules */
.foo {
  & {
    &:hover {
    }
    &:focus {
    }
  }
}

/* Empty at-rule */
@media (min-width: 768px) {
}

/* Empty at-rule with nesting*/
@media (min-width: 768px) {
  .foo {
  }

  @media (min-width: 1024px) {
    .bar {
    }
  }
}
```

None of these will be emitted.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-01-31 17:56:52 +01:00
Jordan Pittman
35a5e8cb64
Discard invalid declarations when parsing CSS (#16093)
I discovered this when triaging an error someone had on Tailwind Play.

1. When we see a `;` we often assume a valid declaration precedes it but
that may not be the case
2. When we see the name of a custom property we assume everything that
follows will be a valid declaration but that is not necessarily the case
3. A bare identifier inside of a rule is treated as a declaration which
is not the case

This PR fixes all three of these by ignoring these invalid cases. Though
some should probably be turned into errors.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-01-31 15:53:02 +01:00
Philipp Spiess
95722020fe
Vite: Transform <style> blocks in html files (#16069)
Fixes #16036

This adds a new rule to treat `<style>` blocks found within `.html` file
as Tailwind CSS targets.

## Test plan

- Tested using the Vite extension (dev) and a new integration test
(prod)

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-01-31 14:26:45 +00:00
Philipp Spiess
deb33a93ab
Vite: Don't rebase urls that appear to be aliases (#16078)
Closes #16039

This PR changes our URL rebasing logic used with Vite so that it does
not rebase URLs that look like common alias paths (e.g. urls starting in
`~`, `@` or `#`, etc.). Unfortunately this is only an approximation and
you can configure an alias for a path that starts with a regular
alphabetical character (e.g. `foo` => `./my/foo`) so this isn't a
perfect fix, however in practice most aliases will be prefixed with a
symbol to make it clear that it's an alias anyways.

One alternative we have considered is to only rebase URLs that we know
are relative (so they need to start with a `.`). This, however, will
break common CSS use cases where urls are loaded like this:

```css
background: image-set(
  url('image1.jpg') 1x,
  url('image2.jpg') 2x
);
``` 

So making this change felt like we only trade one GitHub issue for
another one.

In a more ideal scenario we try to resolve the URL with the Vite
resolver (we have to run the resolver and can't rely on the `resolve`
setting alone due to packages like
[`vite-tsconfig-paths`](https://www.npmjs.com/package/vite-tsconfig-paths)),
however even then we can have relative paths being resolvable to
different files based on wether they were rebased or not (e.g. when an
image with the same filename exists in two different paths).

So ultimately we settled on extending the already existing blocklist
(which we have taken from the Vite implementation) for now.

## Test plan

- Added unit test and it was tested with the Vite playground.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-01-31 15:23:32 +01:00
Philipp Spiess
60e61950b9
Ensure escaped theme variables are handled correctly (#16064)
This PR ensures that escaped theme variables are properly handled. We do
this by moving the `escape`/`unescape` responsibility back into the main
tailwindcss entrypoint that reads and writes from the CSS and making
sure that _all internal state of the `Theme` class are unescaped
classes.

However, due to us accidentally shipping the part where a dot in the
theme variable would translate to an underscore in CSS already, this
logic is going to stay as-is for now.

Here's an example test that visualizes the new changes:

```ts
expect(
  await compileCss(
    css`
      @theme {
        --spacing-*: initial;
        --spacing-1\.5: 2.5rem;
        --spacing-foo\/bar: 3rem;
      }
      @tailwind utilities;
    `,
    ['m-1.5', 'm-foo/bar'],
  ),
).toMatchInlineSnapshot(`
  ":root, :host {
    --spacing-1\.5: 2.5rem;
    --spacing-foo\\/bar: 3rem;
  }

  .m-1\\.5 {
    margin: var(--spacing-1\.5);
  }

  .m-foo\\/bar {
    margin: var(--spacing-foo\\/bar);
  }"
`)
```

## Test plan

- Added a unit test
- Ensure this works end-to-end using the Vite playground:
   
<img width="1016" alt="Screenshot 2025-01-30 at 14 51 05"
src="https://github.com/user-attachments/assets/463c6fd5-793f-4ecc-86d2-5ad40bbb3e74"
/>
2025-01-31 15:20:18 +01:00
Robin Malfait
3aa0e494bf
Do not emit @keyframes in @theme reference (#16120)
This PR fixes na issue where `@keyframes` were emitted if they wre in a
`@theme
reference` and anothe `@theme {}` (that is not a reference) was present.

E.g.:

```css
@reference "tailwindcss";

@theme {
  /* ... */
}
```

Produces:
```css
:root, :host {
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
@keyframes ping {
  75%, 100% {
    transform: scale(2);
    opacity: 0;
  }
}
@keyframes pulse {
  50% {
    opacity: 0.5;
  }
}
@keyframes bounce {
  0%, 100% {
    transform: translateY(-25%);
    animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
  }
  50% {
    transform: none;
    animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
  }
}
```

With this PR, the produced CSS looks like this instead:
```css
:root, :host {
}
```

Note: the empty `:root, :host` will be solved in a subsequent PR.

### Test plan

Added some unit tests, and a dedicated integration test.
2025-01-31 15:13:17 +01:00
Siriwat K
88c890615a
Prevent modifying CSS variables in plugins (#16103)
closes #16100

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-01-31 12:44:30 +01:00
depfu[bot]
d85e9cfcab
Update turbo 2.3.3 → 2.3.4 (patch) (#16084)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ turbo (2.3.3 → 2.3.4) ·
[Repo](https://github.com/turborepo/turbo)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-31 11:01:13 +00:00
depfu[bot]
0589d7d386
Update @playwright/test 1.49.1 → 1.50.0 (minor) (#16091) 2025-01-31 11:22:45 +01:00
Philipp Spiess
c09bb5e256
Fix Vite issues with SolidStart (#16052)
Fixes #16045

This PR fixes two Vite issues found with SolidStart:

- SolidStart seems to emit an empty HTML chunk (where the content is
literally just `/`) with _no pathname_. Since we use the path to
generate an `id` for HTML chunks, this would currently cause a crash.
This was reported in #16045
- While testing the fix for the above, we also found that hot reloading
was not working in SolidStart since `4.0.0-alpha.22`. After doing some
bisecting we found that this is happening as SolidStart has the same
module ID in different servers and we were invalidating the root when we
shouldn't. After trying to restructure this code so that it only cleans
up the root when it is _no longer part of any server_, we noticed some
other compatibility issues with Nuxt and SvelteKit. It seems that the
safest bet is to no longer update a root at all during rebuilds in the
SSR step. This makes `invalidateAllRoots` a function that only notifiers
the servers about a change which is conceptually also less confusing.

## Test plan

- Added an integration test for SolidStart dev mode
- Manually tested the dev mode across all Vite based templates in
https://github.com/philipp-spiess/tailwindcss-playgrounds: Astro, Nuxt,
Remix, Solid, SvelteKit, and Vue.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-01-30 16:58:20 +01:00
Robin Malfait
224294122b
Refactor gradient implementation to work around prettier/prettier#17058 (#16072)
This PR fixes an issue where tools like Prettier remove important
trailing commas in CSS variables, making gradients invalid.

We encoded the `,` in the `--tw-gradient-position` to ensure that _if_
the `var(--tw-gradient-position)` is used, that the `,` was there. And
if the variable was _not_ used that we didn't end up with a double `,,`
rendering the gradient invalid.

However, when running Prettier (there might be other tools that do this
as well), the trailing comma in the `--tw-gradient-position` was removed
which made the entire gradient invalid. E.g.:
```diff
  .bg-gradient-to-r {
-   --tw-gradient-position: to right in oklab,;
+   --tw-gradient-position: to right in oklab;
    background-image: linear-gradient(var(--tw-gradient-stops));
  }
```
Notice how the `,` is removed.

This PR fixes that, by moving the `,` to where the variable is being
used. The only side effect is that we have to guarantee that the
`--tw-gradient-position` is always present. In our testing (and using UI
tests) this should be the case.

Related Prettier issue:
https://github.com/prettier/prettier/issues/17058

Fixes: #16037
2025-01-30 15:46:29 +00:00
Robin Malfait
0d5e2be312
Ensure we process Tailwind CSS features when using @reference (#16057)
This PR fixes an issue where if you only used `@reference` that we
didn't process Tailwind CSS features.

We have a 'quick bail check', in the PostCSS plugin to quickly bail if
we _konw_ that we don't need to handle any Tailwind CSS features. This
is useful in Next.js applications where every single CSS file will be
passed to the PostCSS plugin.

If you use custom font ins Next.js, each of those fonts will have a CSS
file as well.

Before we introduced `@reference`, we used `@import "tailwindcss"
reference`, which passed the bail check because `@import` was being
used. Now we have `@reference` which wasn't included in the list.

This is now solved.

Fixes: #16056


### Test plan

Added a failing test that is now failing after the fix.
2025-01-30 15:29:08 +00:00
Robin Malfait
ea24995749
Only generate positive grid-cols-* and grid-rows-* utilities (#16020)
This PR fixes an issue where `grid-cols-0` and `grid-rows-0` generated
invalid CSS. We now ensure that the value is any positive integer
(except 0).

Fixes: #16012
2025-01-29 19:56:23 +00:00
Philipp Spiess
06552092bd
Prepare v4.0.1 release (#16018)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-01-29 14:14:35 -05:00
Philipp Spiess
a4761ea967
Do not scan tailwind-merge sources for candidates (#16005)
Resolves #15722

This PR adds a list of ignored dependencies (in the current form only
`tailwind-merge`) to the Vite extension which, when included in the
dependency tree, are no longer scanned by the Tailwind CSS compiler for
candidates. This is to work around an issue where some dependencies
contain vast lists of valid Tailwind CSS class names which would
otherwise always be inlined in the build.

## Test plan

This was tested in our Vite playground on both dev and prod builds
across macOS and Windows:

### Windows prod build before

![telegram-cloud-photo-size-4-5823632138052944949-y](https://github.com/user-attachments/assets/a2bca1c7-cef3-4f04-a588-cbf1afdbdd37)

### Windows prod build after

This includes a debug `console.log(…)` to make sure it matches the right
module.

![telegram-cloud-photo-size-4-5823632138052944951-y](https://github.com/user-attachments/assets/109b82c5-1946-4d44-b812-d68474686eb7)

### Windows dev build after

This includes a debug `console.log(…)` to make sure it matches the right
module.

![telegram-cloud-photo-size-4-5823632138052944948-y](https://github.com/user-attachments/assets/41cbe7f1-f997-4c6c-b5fe-01d574f7a999)
2025-01-29 17:32:06 +01:00
Hugo van Rijswijk
9fef2bde50
Add :host rule to @theme layer (#15975)
Resolves #15799
Resolves #14478
Part-of #15005

Adds a `:host` selector for the `@theme` layer. This is necessary for
the `@theme` layer to work correctly in shadow DOM.

Also updates the snapshots for the tests that were affected by this
change (in a separate commit).

## Test plan

Tested via the Vite playground:

<img width="1121" alt="Screenshot 2025-01-29 at 15 06 49"
src="https://github.com/user-attachments/assets/a7908135-5ff8-472f-a053-d2c6d5c81e1b"
/>

Additionally made sure that `@property` defaults also work across
Firefox, Chrome, and Safari (the `@property` definition from the root is
pulled in) and added a UI spec.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-01-29 17:20:29 +01:00
Robin Malfait
924dd69bdb
Ensure @custom-variant foo () has a non-empty selector list (#16009)
This PR fixes an issue where an empty selector list was valid when
defining a `@custom-variant`.

Given this input:
```css
@custom-variant foo ();
```

If you then use it with a utility such as `foo:flex`, then the following
(incorrect) CSS was generated:

```css
.foo\:flex {
   {
    display: flex;
  }
}
```

Which is invalid CSS. This PR will now validate that that we have at
least _something_ and show an error accordingly.
2025-01-29 16:09:09 +01:00
Robin Malfait
965048c01a
Remove the force variant (#16007)
This PR will remove the `force` variant. This was an experiment that we
accidentally shipped, but there is no documentation nor is there any
intellisense autocompletion for it.
2025-01-29 15:32:37 +01:00
Robin Malfait
3da9d61371
Correctly migrate leading-* classes (#16004)
This PR fixes the upgrade tool by properly migrating the
`leading-[<number>]` classes.

The issue is that `leading-[<number>]` maps to the number directly, but
if you use a bare value, then it's a multiplier for based on the
`--spacing` value.

E.g.:

*leading-[2]*:
```css
.leading-\[2\] {
  --tw-leading: 2;
  line-height: 2;
}
@property --tw-leading {
  syntax: "*";
  inherits: false;
}
```

*leading-2*:
```css
.leading-2 {
  --tw-leading: calc(var(--spacing) * 2);
  line-height: calc(var(--spacing) * 2);
}
@property --tw-leading {
  syntax: "*";
  inherits: false;
}
```

This PR will now prevent migrating arbitrary values to bare values for
`leading-*` utilities.

That said, this does introduce a small improvement where `leading-[1]`
is migrated to `leading-none`.

Fixes: https://github.com/tailwindlabs/tailwindcss/issues/15924
2025-01-29 15:22:13 +01:00
Robin Malfait
125c0891b1
update changelog 2025-01-29 11:48:01 +01:00
Xetera
9bfeb337e2
Fix resolve_globs crash when at root directory (#15988)
Fixes a crash found in Dockerfiles when a build takes place at the root
directory. It's not a good practice to keep your application logic in
`/`, but it probably shouldn't cause a crash either.

I found this particularly difficult to write tests for because it would
involve either running a glob on my real filesystem starting from `/` or
mocking the calls to `fs` which as far as I can tell isn't supported in
the codebase and would be out of scope to try to do here.

Fixes #15987
2025-01-29 11:45:33 +01:00
Philipp Spiess
f237f59580 Add missing changelog entry for #15978 2025-01-28 17:38:01 +01:00
Robin Malfait
2d3c196591
Rename next to main (#15980)
This PR renames `next` to `main` in GitHub Action Workflows and the
CONTRIBUTING.md file.
2025-01-28 11:31:54 -05:00
Philipp Spiess
b492187c49
Fix Oxide scanner bugs (#15974)
Fixes #15632
Fixes #15740

This PR fixes a number of Oxide scanner bugs reported over various
channels, specifically:

- When using the Svelte class shorthand split over various lines, we
weren't extracting class names properly:
   
   ```svelte
   <div
     class:underline={isUnderline}>
   </div>
   ```
- We now extract classes when using the class shortcut in Angular:
   
   ```html
   <div [class.underline]=\"bool\"></div>
   ```
- We now validate parentheses within arbitrary candidates so that we
don't consume invalid arbitrary candidates anymore which allows us to
parse the following case properly:
   
   ```js
   const classes = [wrapper("bg-red-500")]
   ```


## Test plan

Added unit tests

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-01-28 17:19:31 +01:00
Jordan Pittman
e02a29fa94
Don’t look at ignore files outside initialized repos (#15941)
Right now, when Oxide is scanning for files, it considers ignore files
in the "root" directory it is scanning as well as all parent
directories.

We honor .gitignore files even when not in a git repo as an optimization
in case a project has been created, contains a .gitignore, but no repo
has actually been initialized. However, this has an unintended side
effect of including ignore files _ouside of a repo_ when there is one.
This means that if you have a .gitignore file in your home folder it'll
get applied even when you're inside a git repo which is not what you'd
expect.

This PR addresses this by checking to see the folder being scanned is
inside a repo and turns on a flag that ensures .gitignore files from the
repo are the only ones used (global ignore files configured in git still
work tho).

This still needs lots of tests to make sure things work as expected.

Fixes #15876

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-01-28 16:13:16 +00:00
Robin Malfait
86264a9e66
Revert "Update bun 1.1.43 → 1.2.0 (minor) (#15922)"
This reverts commit d66d7aba6f.
2025-01-28 17:03:17 +01:00
Philipp Spiess
82ddc249c3
Ensure browser package does not pollute global namespace (#15978)
Resolves #15977

Our `@tailwindcss/browser` build is intended to run inside a `<script>`
tag inside browsers. Because of how variable assignment within
`<script>` tags work, all variables that were defined within that script
are currently assigned on the global namespace.

This is especially troublesome as eslint uses `$` as a valid mangling
character so importing the CDN build would now redefine `globalThis.$`
which collides with many very popular JavaScript libraries.

In order to avoid polluting the global namespace, this PR changes the
build step to emit an IIFE (so all vars defined are scroped to the
function closure instead of the global namespace).

## Test plan

- Ensure UI tests still work
- <img width="533" alt="Screenshot 2025-01-28 at 16 49 27"
src="https://github.com/user-attachments/assets/1e027451-f58b-4252-bf97-c016a90eb78b"
/>
2025-01-28 16:58:08 +01:00
depfu[bot]
b009afa4ae
Update @types/react-dom 19.0.2 → 19.0.3 (patch) (#15669)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react-dom (19.0.2 → 19.0.3) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-28 16:55:56 +01:00
depfu[bot]
d66d7aba6f
Update bun 1.1.43 → 1.2.0 (minor) (#15922)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ bun (1.1.43 → 1.2.0) · [Repo](https://github.com/oven-sh/bun)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-28 15:40:09 +00:00
depfu[bot]
e1c084c1b1
Update all of nextjs 15.1.3 → 15.1.4 (patch) (#15630)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint-config-next (15.1.3 → 15.1.4)





Sorry, we couldn't find anything useful about this release.



#### ✳️ next (15.1.3 → 15.1.4) ·
[Repo](https://github.com/vercel/next.js)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.1.4">15.1.4</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon
octicon-info mr-2" viewbox="0 0 16 16" version="1.1" width="16"
height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0
8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1
7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1
0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0
2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>backport: force module format for virtual client-proxy (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/74608">#74608</a>)</li>
<li>Fix prerender tags when notFound is called (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/74607">#74607</a>)</li>
<li>Use provided waitUntil for pending revalidates (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/74604">#74604</a>)</li>
<li>Feature: next/image: add support for images.qualities in next.config
(<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/74588">#74588</a>)</li>
<li>Chore: docs: add missing search: '' on remotePatterns (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/74587">#74587</a>)</li>
<li>Chore: docs: update version history of next/image (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/73923">#73923</a>)
(<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/74570">#74570</a>)</li>
<li>Chore: next/image: improve imgopt api bypass detection for
unsupported images (<a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/74569">#74569</a>)</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to @ and @ for helping!</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="4cbaaa118d...48f2588b0f">See
the full diff on Github</a>. The new version differs by 8 commits:</p>
<ul>
<li><a
href="48f2588b0f"><code>v15.1.4</code></a></li>
<li><a
href="dcb208e260"><code>backport:
force module format for virtual client-proxy (#74162)
(#74608)</code></a></li>
<li><a
href="6f6766a255"><code>Fix
prerender tags when notFound is called (#74577) (#74607)</code></a></li>
<li><a
href="a46f27f59a"><code>Use
provided waitUntil for pending revalidates (#74164)
(#74604)</code></a></li>
<li><a
href="bc6acf6217"><code>feat(next/image):
add support for `images.qualities` in next.config
(#74588)</code></a></li>
<li><a
href="d253ac5c57"><code>chore(docs):
add missing `search: &#39;&#39;` on `remotePatterns` (#73925)
(#74587)</code></a></li>
<li><a
href="7b195a82a5"><code>chore(docs):
update version history of `next/image` (#73923) (#74570)</code></a></li>
<li><a
href="5a501d37ca"><code>chore(next/image):
improve imgopt api bypass detection for unsupported images
(#74569)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-01-28 16:37:57 +01:00
Jordan Pittman
1eb2980678
Fix IntelliSense class suggestions (#15857)
This PR fixes a few problems with IntelliSense:
- Default installs suggest font weight utilities twice
- When setting `--font-weight-*: initial;` the default weights are still
suggested
- Setting `--spacing-*: initial;` still shows suggestions for utilities
using the spacing scale assuming it's multiplier-based. Removing the
`--spacing: …` key should make them entirely theme driven instead.
- The default container query variants like `@sm`, `@md`, etc… aren't
suggested. Only the `@min-sm` and `@max-sm` style ones are.

Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1133
Fixes
https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1136
Fixes #15811

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-01-28 10:35:11 -05:00
Robin Malfait
28008f1571
remove unnecessary version step
My bad.
2025-01-28 16:34:01 +01:00
Robin Malfait
35bc28b16b
Ensure we set the version before running build (#15971)
This PR ensures we set the version before running the build. Otherwise
the embedded version number is incorrect.
2025-01-28 10:21:18 -05:00
Robin Malfait
515a9bdc5f
Setup v4 releases (#15961)
Now that Tailwind CSS v4 is released, we can setup a proper release
workflow again. This setup mimics the workflow of how we did it in v3,
but adjusted to make it work on the v4 codebase.

Whenever a PR is merged into the `next` branch, we will publish an
insiders release to npm using the following version number:
`0.0.0-insiders.<commit-hash>`. Note: insiders releases will not have a
GitHub release associated with them, therefore the `standalone-cli`
won't be available as an insiders release.

For the normal release procedure, the following steps are required:

1. Manually version the packages (e.g.: `pnpm run version-packages`)
2. Create a git tag for the version you want to release
3. Push the updated package.json files and the git tag to the repository

Next, a GitHub action will kick in once a `tag` is being pushed.

The GitHub action will run a build, and create a draft release on GitHub
that will contain:

1. The CHANGELOG.md contents for the last version
2. The `standalone-cli` artifacts attached to the drafted release

Once you are happy with the draft, you can publish the draft on GitHub.

This in turn will trigger another GitHub action that will publish the
packages to npm.

Whenever an insiders release or a normal release completes, we will also
trigger Tailwind Play, to update its dependencies to the latest version
of Tailwind CSS.

---

Note: some of the GitHub Action workflows still refer to the `next`
branch instead of the `main` branch. If we later today want to get a new
release out, we can merge `next` into `main` and update the workflows
accordingly.


---

This is hard to test, but I started from the existing release.yml file
and adjusted things accordingly. The biggest change is related to the
insiders version. If you look at this temporary
[commit](572dddfc33),
you can see that the publishing (dry-run) seems to work as expected:
<img width="1508" alt="image"
src="https://github.com/user-attachments/assets/c075e788-dcbc-4200-aa32-2b9a3c54d629"
/>
2025-01-28 12:51:34 +01:00
Philipp Spiess
2c1dea4c1a
Fix handling of absolute config files in upgrade tool (#15927)
Closes #15220

This PR fixes an issue where the upgrade tool would not be able to load
some JavaScript config files across different drive letters on Windows.

The issue in detail is that `path.relative(…)` tries to build a relative
path but if the file is inside the same folder, it won't start the
relative path with a `./` so we manually appended it in case that it
isn't there. The issue on Windows specifically is that
`file.relative(…)` can also return a legit absolute path, e.g. when the
file is on a different drive. In this case we obviously don't want to
prefix a path with `./`.

## Test plan

To reproduce this issue, I checked out a Tailwind v3 project _on a
different drive letter than my Windows installation_. In that case, I
was adding a repo inside `D:` while `npm` was installed in `C:`. I then
run `npx @tailwindcss/upgrade` to reproduce the issue.

The fix was validated with a local `bun` run of the upgrade tool:


![telegram-cloud-photo-size-4-5818901845756725194-y](https://github.com/user-attachments/assets/d32b21e3-a08d-4608-b65a-93dddc04f890)
2025-01-28 12:42:30 +01:00
Robin Malfait
f1221b3fce
Ensure font-size utilities with none modifier works e.g.: text-sm/none (#15921)
This PR fixes an issue where classes such as `text-sm/none` don't work
as expected. The reason for this is that `leading-none` is the only
hardcoded leading utility and is not coming from the `@theme`. This
means that `text-sm/none` tries to do a lookup for `none` but it won't
resolve.

This PR fixes that by allowing `none` as a modifier.

While working on this, I noticed that `text-sm/none` _did_ generate CSS:

```css
.text-sm\/none {
  font-size: var(--text-sm);
}
```

Notice that the `line-height` is missing. This means that any modifier
that can't be resolved doesn't get the `line-height` set, but it _will_
generate CSS. In this case, no CSS should have been generated.

Otherwise, all of these generate CSS which will only bloat your CSS and
won't
work as expected. E.g.: `text-sm/foo`, `text-sm/bar`, and `text-sm/baz`:

```css
.text-sm\/bar {
  font-size: var(--text-sm);
}
.text-sm\/baz {
  font-size: var(--text-sm);
}
.text-sm\/foo {
  font-size: var(--text-sm);
}
```

Fixes: #15911
2025-01-27 14:10:09 +01:00
Luke Warlow
6dd4c334c2
Include :open in the open variant (#15349)
Update the `open` variant to addtionally match the new `:open` pseudo
class.

See spec at https://drafts.csswg.org/selectors-4/#open-state

See Chromium intent to ship:
https://groups.google.com/a/chromium.org/g/blink-dev/c/Pdo4oOXYKcA

`:open` matches open `<details>` and `<dialog>` elements (already
covered by [open]), but also matches select elements with their picker
open and inputs with their picker open.

Thanks to `:is()` taking a forgiving selector list, this is safe to add
even with limited browser support.

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-01-27 12:43:12 +00:00
depfu[bot]
a707fed089
Update eslint 9.17.0 → 9.18.0 (minor) (#15664) 2025-01-27 13:30:56 +01:00
depfu[bot]
14fea72016
Update @types/react 19.0.2 → 19.0.7 (patch) (#15682) 2025-01-27 13:28:43 +01:00
Robin Malfait
193fc60989
Ensure CSS variable shorthand uses valid CSS variables (#15738)
This PR fixes an issue where we didn't always correctly validated
invalid CSS variables when using the CSS variable shorthand syntax.

This PR fixes that by ensuring we start with `--`. We can't validate
that the variable exists at runtime, but we can validate if it looks
like a valid CSS variable.

We can go a step further by validating if the CSS variable is valid
syntax (e.g.: all characters are valid), but we can introduce this if
this is causing actual issues in projects.

```
p-(a-b)
```

Used to compile to:
```css
.p-\(a-b\) {
  padding: var(a-b);
}
```

But not anymore.
2025-01-27 12:55:10 +01:00
Robin Malfait
79541c94d9
Merge changelogs (#15698)
This PR makes sure all v3 and earlier versions are visible in the
changelog as
well as the v4 changelog entries.
2025-01-27 12:54:26 +01:00
Justin Wong
7e20c3b587
Remove min-w/h-none (#15845)
`none` is not a valid value for `min-width` or `min-height` according to
[the CSS spec](https://www.w3.org/TR/css-sizing-3/#min-size-properties):

> | | |
> | --- | --- |
> | [Value](https://www.w3.org/TR/css-values/#value-defs): | auto \|
[&lt;length-percentage>](https://www.w3.org/TR/css-values-4/#typedef-length-percentage)
\| min-content \| max-content \|
fit-content([&lt;length-percentage>](https://www.w3.org/TR/css-values-4/#typedef-length-percentage))
|

I believe we should be able to remove the `min-w-none` and `min-h-none`
class candidates since they wouldn't have done anything anyway and thus
should not affect backwards compatibility. Indeed, these did not exist
in v3 either:


4f9f603e12/stubs/config.full.js (L674-L684)


4f9f603e12/stubs/config.full.js (L685-L691)

---

Credit to `@i` on Discord for spotting this[^1], fixes #15846

[^1]:
1332680061

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-01-25 17:04:55 +00:00
Jordan Pittman
a8c54acaba
Prepare v4.0.0 release (#15693) 2025-01-21 20:58:59 +00:00
Robin Malfait
8a97a6a8d9
v4.0.0-beta.10 (#15691)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-01-21 16:19:28 +00:00
Robin Malfait
4820f6bff8
Transition outline-color (#15690)
This PR adds `outline-color` to the transition-property again, and
updates the implementation for `outline-hidden`.

The `outline-hidden` will behave exactly the same as `outline-none`
_except_ in forced colors mode. In forced colors mode, `outline-hidden`
will force a 2px solid border.

Closes: #15591
2025-01-21 11:02:57 -05:00
Robin Malfait
d65bb68d16
update changelog 2025-01-21 10:40:22 -05:00
Jordan Pittman
79f21a8f45
Prevent not-* from being used with variants with multiple sibling rules (#15689)
Variants like this can't be easily negated by our current system:

```css
@custom-variant dark {
  &.is-dark {
    @slot;
  }
  @media (prefers-color-scheme: dark) {
    @slot;
  }
}
```

Right now it produces the following CSS which is logically incorrect:

```css
.utility-name {
  &:not(.is-dark) {
    /* ... */
  }

  @media not (prefers-color-scheme: dark) {
    /* ... */
  }
}
```

The correct CSS is this which requires moving things around:
```css
.utility-name {
  @media not (prefers-color-scheme: dark) {
    &:not(.is-dark) {
      /* ... */
    }
  }
}
```

We're opting to disable this instead of generating incorrect CSS for
now. I'd like to bring this back in the future for simpler cases in the
future.
2025-01-21 10:38:32 -05:00
Robin Malfait
4035ab0b76
Implement @variant (#15663)
This PR replaces `@variant` with `@custom-variant` for registering
custom variants via your CSS.

In addition, this PR introduces `@variant` that can be used in your CSS
to use a variant while writing custom CSS.

E.g.:

```css
.btn {
  background: white;

  @variant dark {
    background: black;
  }
}
```

Compiles to:

```css
.btn {
  background: white;
}

@media (prefers-color-scheme: dark) {
  .btn {
    background: black;
  }
}
```

For backwards compatibility, the `@variant` rules that don't have a body
and are
defined inline:

```css
@variant hocus (&:hover, &:focus);
```

And `@variant` rules that are defined with a body and a `@slot`:

```css
@variant hocus {
  &:hover, &:focus {
    @slot;
  }
}
```

Will automatically be upgraded to `@custom-variant` internally, so no
breaking changes are introduced with this PR.

---

TODO:
- [x] ~~Decide whether we want to allow multiple variants and if so,
what syntax should be used. If not, nesting `@variant <variant> {}` will
be the way to go.~~ Only a single `@variant <variant>` can be used, if
you want to use multiple, nesting should be used:

```css
.foo {
  @variant hover {
    @variant focus {
      color: red;
    }
  }
}
```
2025-01-21 10:20:35 -05:00
depfu[bot]
67237e22a1
Update postcss 8.4.49 → 8.5.1 (minor) (#15675) 2025-01-20 21:50:29 +01:00
depfu[bot]
03afd43f28
Update h3 1.13.0 → 1.13.1 (patch) (#15661) 2025-01-20 15:39:26 +01:00
Robin Malfait
a51b214c13
Use more modern --alpha(color / 50%) syntax (#15665)
This PR changes the syntax for the `--alpha(…)` function to look like
more modern CSS.

The arguments to apply an alpha to a color is using the `/` syntax
instead of the comma syntax.

```diff
- --alpha(color, 50%)
+ --alpha(color / 50%)
```

This syntax is now similar to modern `rgb(0 0 0 / 50%)` syntax in CSS.
2025-01-17 18:48:18 -05:00
Robin Malfait
f93c42fcfc
Write to stdout when --output is omitted (#15656) 2025-01-17 15:23:34 +00:00
Jordan Pittman
2f8c51774e
Ensure -outline-offset-* utilities are suggested in IntelliSense (#15646)
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-01-16 12:09:56 -05:00
Philipp Spiess
fe758af73d
Upgrade lightningcss to 1.29.1 in standalone package (#15641)
Closes #15636
Closes #15638
Closes #15637

I noticed that I forgot to update the bundled `lightningcss`
dependencies in the `standalone` project. This makes sure we have the
same version everywhere.
2025-01-16 16:06:08 +01:00
Jordan Pittman
528c848575
Discard invalid variants such as data-checked-[selected=1]:* (#15629) 2025-01-15 09:55:39 -05:00
depfu[bot]
e62991abf9 Update @tailwindcss/typography to version 0.5.16 2025-01-15 09:58:48 +00:00
depfu[bot]
79b471500a
Update @tailwindcss/forms 0.5.9 → 0.5.10 (minor) (#15627) 2025-01-15 10:50:29 +01:00
Philipp Spiess
2de644b20e
Remove @property fallbacks for Firefox (#15622)
This PR removes the `@property` fallbacks added in #13655. This is
possible because we're targeting a minimum Firefox version of 128 which
[includes support for \`@property\`
rules](https://developer.mozilla.org/de/docs/Web/CSS/@property).

<img width="1284" alt="Screenshot 2025-01-14 at 11 36 44"
src="https://github.com/user-attachments/assets/ae070781-35c1-4165-be51-baa63f28db5b"
/>
2025-01-14 15:51:44 +01:00
Robin Malfait
da2da51284
Resolve values in functional utilities based on @theme options (#15623)
This PR fixes an issue where functional utilities configured via CSS
don't resolve the values correctly.

We always fully resolved the values as-if a `@theme inline` was used.

We used to compile the following code:
```css
@theme {
  --tab-size-github: 8;
}

@utility tab-* {
  tab-size: --value(--tab-size);
}
```

Into:
```css
:root {
  --tab-size-github: 8;
}

.tab-github {
  tab-size: 8;
}
```

But it should be referencing the variable instead:
```css
:root {
  --tab-size-github: 8;
}

.tab-github {
  tab-size: var(--tab-size-github);
}
```

However, if you used `@theme inline reference`, it should inline the
value:
```css
@theme inline reference {
  --tab-size-github: 8;
}

@utility tab-* {
  tab-size: --value(--tab-size);
}
```

This will now correctly compile to:
```css
.tab-github {
  tab-size: 8;
}
```
2025-01-14 12:35:04 +01:00
depfu[bot]
d2fbdf505f
Update @types/bun 1.1.14 → 1.1.16 (patch) (#15618) 2025-01-14 11:25:18 +01:00
Robin Malfait
3b033957c5
Ensure namespace reset with escaped * works (#15603)
This PR fixes an issue if you want to reset a namespace like:

```css
@theme {
  --color-*: initial;
}
```

That some formatters such as Biome won't allow this syntax. To solve
that, this PR allows you to use an escaped `*` character.

```css
@theme {
  --color-\*: initial;
}
```

Fixes: #15602
2025-01-13 10:43:22 +00:00
depfu[bot]
ae8fb146a7
Update fast-glob 3.3.2 → 3.3.3 (patch) (#15607) 2025-01-13 11:11:35 +01:00
depfu[bot]
fcee63d997
Update @types/braces 3.0.4 → 3.0.5 (patch) (#15612) 2025-01-13 10:53:47 +01:00
Robin Malfait
b4a20af4d0
Improve upgrade tool to pretty print --spacing(2) (#15596)
This PR improves the upgrade tool to make sure that newly upgraded
`--spacing(2)` CSS functions is pretty printed to prevent unambiguous
looking classes (even though it compiles correctly).

If you have a class such as `m-[calc(100dvh-theme(spacing.2))]`, then we
used to convert it to `m-[calc(100dvh-calc(var(--spacing)*2))]`. But
recently we introduced the `--spacing(2)` CSS function which means that
the output now looks like this instead: `m-[calc(100dvh---spacing(2))]`.

The triple `-` is valid because the first `-` is the minus sign, the
next two `-` characters are from the function.

One solution is to introduce spaces via underscores:

```
m-[calc(100dvh_-_--spacing(2))]
```

But a simpler solution, is to wrap the `--spacing(2)` in parens to
remove the underscores and improve the readability of the `---`
characters.

```
m-[calc(100dvh-(--spacing(2)))]
```
2025-01-10 09:07:23 -05:00
depfu[bot]
27f8bab7ab
Update @types/react 18.3.12 → 19.0.2 (major) (#15588)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react (18.3.12 → 19.0.2) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-10 13:14:27 +00:00
depfu[bot]
def9bf3406
Update all of react 18.3.1 → 19.0.0 (major) (#15586)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ react (18.3.1 → 19.0.0) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.0.0">19.0.0</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="f1338f8080...7aa5dda3b3">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>




#### ✳️ react-dom (18.3.1 → 19.0.0) ·
[Repo](https://github.com/facebook/react) ·
[Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/facebook/react/releases/tag/v19.0.0">19.0.0</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="f1338f8080...7aa5dda3b3">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-10 14:10:23 +01:00
depfu[bot]
501c59f7aa
Update @types/react-dom 18.3.1 → 19.0.2 (major) (#15587)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/react-dom (18.3.1 → 19.0.2) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-10 14:10:04 +01:00
Philipp Spiess
acd2da5247
Upgrade lightningcss to 1.29.1 (#15593)
Upgrading `lightningcss` to fix invalid `list-style: none` conversion.
I've also reverted the change to preflight while at it, since it's no
longer necessary.
2025-01-10 14:09:13 +01:00
Robin Malfait
5df5daa76c
Add missing main field for @tailwindcss/browser (#15594)
This PR adds a `main` and `browser` field for the `@tailwindcss/browser`
package.

In the package, we do have the `exports` field setup, which is an
alternative to the `main` field according to the docs:

> The "exports" provides a modern alternative to "main" …
>
> —
https://docs.npmjs.com/cli/v10/configuring-npm/package-json?v=true#exports

However, if you look at the unpkg link:
https://unpkg.com/@tailwindcss/browser, it tries to load the `index.js`
file. This is probably a bug in the unpkg resolver.

That said, if you look at other CDNs such as esm.sh, it does resolve
correctly: https://esm.sh/@tailwindcss/browser

According to the npm docs:

> If `main` is not set, it defaults to `index.js` in the package's root
folder.
>
> —
https://docs.npmjs.com/cli/v10/configuring-npm/package-json?v=true#main

This explains why unpkg is trying to load the `index.js` file.


Additionally, the npm docs also mention the `browser` field:

> If your module is meant to be used client-side the browser field
should be used instead of the main field. This is helpful to hint users
that it might rely on primitives that aren't available in Node.js
modules. (e.g. window)
>
> —
https://docs.npmjs.com/cli/v10/configuring-npm/package-json?v=true#browser

So this PR also adds that field just to be sure.
2025-01-10 07:39:00 -05:00
depfu[bot]
0ed4d81925
Update tree-sitter 0.22.1 → 0.22.4 (minor) (#15585) 2025-01-10 10:26:48 +01:00
Robin Malfait
aac8c5a12a
Prepare v4.0.0-beta.9 release (#15583)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-01-09 17:04:34 +00:00
depfu[bot]
2cc4864a79
Update eslint 9.15.0 → 9.17.0 (minor) (#15584)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint (9.15.0 → 9.17.0) ·
[Repo](https://github.com/eslint/eslint) ·
[Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.17.0">9.17.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="eed91d12d4"><code
class="notranslate">eed91d1</code></a> feat: add suggestions to <code
class="notranslate">no-unused-vars</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/18352">#18352</a>)
(Tanuj Kanti)</li>
</ul>
<h2 dir="auto">Bug Fixes</h2>
<ul dir="auto">
<li>
<a
href="67d683df29"><code
class="notranslate">67d683d</code></a> fix: fix crash when <code
class="notranslate">message.fix</code> is nullish (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19168">#19168</a>)
(ntnyq)</li>
<li>
<a
href="c61870715e"><code
class="notranslate">c618707</code></a> fix: ignore vars with
non-identifier references in no-useless-assignment (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19200">#19200</a>)
(YeonJuan)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="3c22d2acce"><code
class="notranslate">3c22d2a</code></a> docs: update <code
class="notranslate">yoda</code> to <code class="notranslate">Yoda</code>
in <code class="notranslate">yoda.md</code> for consistency (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19230">#19230</a>)
(루밀LuMir)</li>
<li>
<a
href="e0a220355f"><code
class="notranslate">e0a2203</code></a> docs: add missing backticks to
<code class="notranslate">no-sequences</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19233">#19233</a>)
(루밀LuMir)</li>
<li>
<a
href="4cc48812cd"><code
class="notranslate">4cc4881</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="3db6fdf885"><code
class="notranslate">3db6fdf</code></a> docs: [no-await-in-loop] expand
on benefits and inapplicability (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19211">#19211</a>)
(Kirk Waiblinger)</li>
<li>
<a
href="bf2a4f686b"><code
class="notranslate">bf2a4f6</code></a> docs: add missing backticks to
<code class="notranslate">func-style</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19227">#19227</a>)
(루밀LuMir)</li>
<li>
<a
href="ba098bd03c"><code
class="notranslate">ba098bd</code></a> docs: add missing header to <code
class="notranslate">prefer-spread</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19224">#19224</a>)
(루밀LuMir)</li>
<li>
<a
href="b607ae6491"><code
class="notranslate">b607ae6</code></a> docs: update description of <code
class="notranslate">no-param-reassign</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19220">#19220</a>)
(루밀LuMir)</li>
<li>
<a
href="1eb424de55"><code
class="notranslate">1eb424d</code></a> docs: add missing backticks to
<code class="notranslate">prefer-destructuring</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19223">#19223</a>)
(루밀LuMir)</li>
<li>
<a
href="85998d1405"><code
class="notranslate">85998d1</code></a> docs: add missing backticks to
<code class="notranslate">no-unneeded-ternary</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19222">#19222</a>)
(루밀LuMir)</li>
<li>
<a
href="b75b32c091"><code
class="notranslate">b75b32c</code></a> docs: add missing backticks to
<code class="notranslate">no-new-func</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19219">#19219</a>)
(루밀LuMir)</li>
<li>
<a
href="a7700bcc9c"><code
class="notranslate">a7700bc</code></a> docs: add missing backticks to
<code class="notranslate">id-length</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19217">#19217</a>)
(루밀LuMir)</li>
<li>
<a
href="e2bb429974"><code
class="notranslate">e2bb429</code></a> docs: add missing backticks to
<code class="notranslate">complexity.md</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19214">#19214</a>)
(루밀LuMir)</li>
<li>
<a
href="045d716b92"><code
class="notranslate">045d716</code></a> docs: add missing <code
class="notranslate">)</code> to <code
class="notranslate">id-denylist</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19213">#19213</a>)
(루밀LuMir)</li>
<li>
<a
href="7fe4114be2"><code
class="notranslate">7fe4114</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="c743ba6402"><code
class="notranslate">c743ba6</code></a> docs: add CSS language to <code
class="notranslate">no-restricted-syntax</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19208">#19208</a>)
(Milos Djermanovic)</li>
<li>
<a
href="1416d70d43"><code
class="notranslate">1416d70</code></a> docs: add missing backticks to
<code class="notranslate">eqeqeq</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19207">#19207</a>)
(루밀LuMir)</li>
<li>
<a
href="b950c1b7db"><code
class="notranslate">b950c1b</code></a> docs: add missing backticks to
<code class="notranslate">prefer-object-spread</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19206">#19206</a>)
(루밀LuMir)</li>
<li>
<a
href="8a941cb776"><code
class="notranslate">8a941cb</code></a> docs: update docs and <code
class="notranslate">description</code> of <code
class="notranslate">require-unicode-regexp</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19205">#19205</a>)
(루밀LuMir)</li>
<li>
<a
href="cbab2281dd"><code
class="notranslate">cbab228</code></a> docs: Update README (GitHub
Actions Bot)</li>
<li>
<a
href="f2257ce412"><code
class="notranslate">f2257ce</code></a> docs: update comments and <code
class="notranslate">description</code> of <code
class="notranslate">no-script-url</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19203">#19203</a>)
(루밀LuMir)</li>
<li>
<a
href="365f0f4df7"><code
class="notranslate">365f0f4</code></a> docs: add missing backtick to
<code class="notranslate">default-case-last</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19202">#19202</a>)
(루밀LuMir)</li>
<li>
<a
href="e6b84f535e"><code
class="notranslate">e6b84f5</code></a> docs: add missing punctuation in
document (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19161">#19161</a>)
(루밀LuMir)</li>
<li>
<a
href="c88708e057"><code
class="notranslate">c88708e</code></a> docs: replace quote with backtick
in <code class="notranslate">description</code> of <code
class="notranslate">for-direction</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19199">#19199</a>)
(루밀LuMir)</li>
<li>
<a
href="a76f233a67"><code
class="notranslate">a76f233</code></a> docs: use higher contrast color
tokens for code comments (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19187">#19187</a>)
(Josh Goldberg ✨)</li>
<li>
<a
href="db19502497"><code
class="notranslate">db19502</code></a> docs: Update README (GitHub
Actions Bot)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="cc243c9482"><code
class="notranslate">cc243c9</code></a> chore: upgrade to <code
class="notranslate">@eslint/js@9.17.0</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19242">#19242</a>)
(Francesco Trotta)</li>
<li>
<a
href="84c5787780"><code
class="notranslate">84c5787</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="4c4f53b8c9"><code
class="notranslate">4c4f53b</code></a> chore: add missing backticks to
<code class="notranslate">flags.js</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19226">#19226</a>)
(루밀LuMir)</li>
<li>
<a
href="4b3132c3f5"><code
class="notranslate">4b3132c</code></a> chore: update dependency
eslint-plugin-expect-type to ^0.6.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19221">#19221</a>)
(renovate[bot])</li>
<li>
<a
href="9bf220464a"><code
class="notranslate">9bf2204</code></a> chore: add type definitions for
the <code class="notranslate">eslint-config-eslint</code> package (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19050">#19050</a>)
(Arya Emami)</li>
<li>
<a
href="ee8c2200a1"><code
class="notranslate">ee8c220</code></a> chore: fix incorrect <code
class="notranslate">name</code> property in <code
class="notranslate">integration-tutorial-code</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19218">#19218</a>)
(루밀LuMir)</li>
<li>
<a
href="cca801dd17"><code
class="notranslate">cca801d</code></a> chore: Upgrade cross-spawn to
7.0.6 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19185">#19185</a>)
(folortin)</li>
</ul></blockquote>
<h4><a
href="https://github.com/eslint/eslint/releases/tag/v9.16.0">9.16.0</a></h4>

<blockquote><h2 dir="auto">Features</h2>
<ul dir="auto">
<li>
<a
href="8f70eb142c"><code
class="notranslate">8f70eb1</code></a> feat: Add <code
class="notranslate">ignoreComputedKeys</code> option in <code
class="notranslate">sort-keys</code> rule (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19162">#19162</a>)
(Milos Djermanovic)</li>
</ul>
<h2 dir="auto">Documentation</h2>
<ul dir="auto">
<li>
<a
href="9eefc8f813"><code
class="notranslate">9eefc8f</code></a> docs: fix typos in <code
class="notranslate">use-isnan</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19190">#19190</a>)
(루밀LuMir)</li>
<li>
<a
href="0c8cea8c80"><code
class="notranslate">0c8cea8</code></a> docs: switch the order of words
in <code class="notranslate">no-unreachable</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19189">#19189</a>)
(루밀LuMir)</li>
<li>
<a
href="0c19417c64"><code
class="notranslate">0c19417</code></a> docs: add missing backtick to
<code class="notranslate">no-async-promise-executor</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19188">#19188</a>)
(루밀LuMir)</li>
<li>
<a
href="8df927646c"><code
class="notranslate">8df9276</code></a> docs: add backtick in <code
class="notranslate">-0</code> in <code
class="notranslate">description</code> of <code
class="notranslate">no-compare-neg-zero</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19186">#19186</a>)
(루밀LuMir)</li>
<li>
<a
href="7e16e3fb85"><code
class="notranslate">7e16e3f</code></a> docs: fix <code
class="notranslate">caseSensitive</code> option's title of <code
class="notranslate">sort-keys</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19183">#19183</a>)
(Tanuj Kanti)</li>
<li>
<a
href="0c6b842121"><code
class="notranslate">0c6b842</code></a> docs: fix typos in <code
class="notranslate">migration-guide.md</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19180">#19180</a>)
(루밀LuMir)</li>
<li>
<a
href="353266edf8"><code
class="notranslate">353266e</code></a> docs: fix a typo in <code
class="notranslate">debug.md</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19179">#19179</a>)
(루밀LuMir)</li>
<li>
<a
href="5ff318a528"><code
class="notranslate">5ff318a</code></a> docs: delete unnecessary
horizontal rule(<code class="notranslate">---</code>) in <code
class="notranslate">nodejs-api</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19175">#19175</a>)
(루밀LuMir)</li>
<li>
<a
href="576bcc5461"><code
class="notranslate">576bcc5</code></a> docs: mark more rules as handled
by TypeScript (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19164">#19164</a>)
(Tanuj Kanti)</li>
<li>
<a
href="742d054ac1"><code
class="notranslate">742d054</code></a> docs: note that <code
class="notranslate">no-restricted-syntax</code> can be used with any
language (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19148">#19148</a>)
(Milos Djermanovic)</li>
</ul>
<h2 dir="auto">Chores</h2>
<ul dir="auto">
<li>
<a
href="feb703b3dc"><code
class="notranslate">feb703b</code></a> chore: upgrade to <code
class="notranslate">@eslint/js@9.16.0</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19195">#19195</a>)
(Francesco Trotta)</li>
<li>
<a
href="df9bf9519a"><code
class="notranslate">df9bf95</code></a> chore: package.json update for
@eslint/js release (Jenkins)</li>
<li>
<a
href="f831893b6e"><code
class="notranslate">f831893</code></a> chore: add type for <code
class="notranslate">ignoreComputedKeys</code> option of <code
class="notranslate">sort-keys</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19184">#19184</a>)
(Tanuj Kanti)</li>
<li>
<a
href="3afb8a1dcf"><code
class="notranslate">3afb8a1</code></a> chore: update dependency
@eslint/json to ^0.8.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19177">#19177</a>)
(Milos Djermanovic)</li>
<li>
<a
href="1f77c53b12"><code
class="notranslate">1f77c53</code></a> chore: add <code
class="notranslate">repository.directory</code> property to <code
class="notranslate">package.json</code> (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19165">#19165</a>)
(루밀LuMir)</li>
<li>
<a
href="d46059410a"><code
class="notranslate">d460594</code></a> chore: update dependency
@arethetypeswrong/cli to ^0.17.0 (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19147">#19147</a>)
(renovate[bot])</li>
<li>
<a
href="45cd4ead9c"><code
class="notranslate">45cd4ea</code></a> refactor: update default options
in rules (<a
href="https://bounce.depfu.com/github.com/eslint/eslint/pull/19136">#19136</a>)
(Milos Djermanovic)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="6f37b0747a...90c1db9a96">See
the full diff on Github</a>. The new version differs by 57 commits:</p>
<ul>
<li><a
href="90c1db9a96"><code>9.17.0</code></a></li>
<li><a
href="755512d831"><code>Build:
changelog update for 9.17.0</code></a></li>
<li><a
href="cc243c9482"><code>chore:
upgrade to `@eslint/js@9.17.0` (#19242)</code></a></li>
<li><a
href="84c5787780"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="eed91d12d4"><code>feat:
add suggestions to `no-unused-vars` (#18352)</code></a></li>
<li><a
href="4c4f53b8c9"><code>chore:
add missing backticks to `flags.js` (#19226)</code></a></li>
<li><a
href="3c22d2acce"><code>docs:
update `yoda` to `Yoda` in `yoda.md` for consistency
(#19230)</code></a></li>
<li><a
href="e0a220355f"><code>docs:
add missing backticks to `no-sequences` (#19233)</code></a></li>
<li><a
href="4cc48812cd"><code>docs:
Update README</code></a></li>
<li><a
href="3db6fdf885"><code>docs:
[no-await-in-loop] expand on benefits and inapplicability
(#19211)</code></a></li>
<li><a
href="67d683df29"><code>fix:
fix crash when `message.fix` is nullish (#19168)</code></a></li>
<li><a
href="bf2a4f686b"><code>docs:
add missing backticks to `func-style` (#19227)</code></a></li>
<li><a
href="4b3132c3f5"><code>chore:
update dependency eslint-plugin-expect-type to ^0.6.0
(#19221)</code></a></li>
<li><a
href="9bf220464a"><code>chore:
add type definitions for the `eslint-config-eslint` package
(#19050)</code></a></li>
<li><a
href="ba098bd03c"><code>docs:
add missing header to `prefer-spread` (#19224)</code></a></li>
<li><a
href="b607ae6491"><code>docs:
update description of `no-param-reassign` (#19220)</code></a></li>
<li><a
href="1eb424de55"><code>docs:
add missing backticks to `prefer-destructuring` (#19223)</code></a></li>
<li><a
href="85998d1405"><code>docs:
add missing backticks to `no-unneeded-ternary` (#19222)</code></a></li>
<li><a
href="ee8c2200a1"><code>chore:
fix incorrect `name` property in `integration-tutorial-code`
(#19218)</code></a></li>
<li><a
href="b75b32c091"><code>docs:
add missing backticks to `no-new-func` (#19219)</code></a></li>
<li><a
href="a7700bcc9c"><code>docs:
add missing backticks to `id-length` (#19217)</code></a></li>
<li><a
href="c61870715e"><code>fix:
ignore vars with non-identifier references in no-useless-assignment
(#19200)</code></a></li>
<li><a
href="e2bb429974"><code>docs:
add missing backticks to `complexity.md` (#19214)</code></a></li>
<li><a
href="045d716b92"><code>docs:
add missing `)` to `id-denylist` (#19213)</code></a></li>
<li><a
href="7fe4114be2"><code>docs:
Update README</code></a></li>
<li><a
href="c743ba6402"><code>docs:
add CSS language to `no-restricted-syntax` (#19208)</code></a></li>
<li><a
href="cca801dd17"><code>chore:
Upgrade cross-spawn to 7.0.6 (#19185)</code></a></li>
<li><a
href="1416d70d43"><code>docs:
add missing backticks to `eqeqeq` (#19207)</code></a></li>
<li><a
href="b950c1b7db"><code>docs:
add missing backticks to `prefer-object-spread` (#19206)</code></a></li>
<li><a
href="8a941cb776"><code>docs:
update docs and `description` of `require-unicode-regexp`
(#19205)</code></a></li>
<li><a
href="cbab2281dd"><code>docs:
Update README</code></a></li>
<li><a
href="f2257ce412"><code>docs:
update comments and `description` of `no-script-url`
(#19203)</code></a></li>
<li><a
href="365f0f4df7"><code>docs:
add missing backtick to `default-case-last` (#19202)</code></a></li>
<li><a
href="e6b84f535e"><code>docs:
add missing punctuation in document (#19161)</code></a></li>
<li><a
href="c88708e057"><code>docs:
replace quote with backtick in `description` of `for-direction`
(#19199)</code></a></li>
<li><a
href="a76f233a67"><code>docs:
use higher contrast color tokens for code comments
(#19187)</code></a></li>
<li><a
href="db19502497"><code>docs:
Update README</code></a></li>
<li><a
href="cbf7db0ddf"><code>9.16.0</code></a></li>
<li><a
href="715ba8b5cb"><code>Build:
changelog update for 9.16.0</code></a></li>
<li><a
href="feb703b3dc"><code>chore:
upgrade to `@eslint/js@9.16.0` (#19195)</code></a></li>
<li><a
href="df9bf9519a"><code>chore:
package.json update for @eslint/js release</code></a></li>
<li><a
href="9eefc8f813"><code>docs:
fix typos in `use-isnan` (#19190)</code></a></li>
<li><a
href="0c8cea8c80"><code>docs:
switch the order of words in `no-unreachable` (#19189)</code></a></li>
<li><a
href="0c19417c64"><code>docs:
add missing backtick to `no-async-promise-executor`
(#19188)</code></a></li>
<li><a
href="8df927646c"><code>docs:
add backtick in `-0` in `description` of `no-compare-neg-zero`
(#19186)</code></a></li>
<li><a
href="7e16e3fb85"><code>docs:
fix `caseSensitive` option&#39;s title of `sort-keys`
(#19183)</code></a></li>
<li><a
href="f831893b6e"><code>chore:
add type for `ignoreComputedKeys` option of `sort-keys`
(#19184)</code></a></li>
<li><a
href="8f70eb142c"><code>feat:
Add `ignoreComputedKeys` option in `sort-keys` rule
(#19162)</code></a></li>
<li><a
href="0c6b842121"><code>docs:
fix typos in `migration-guide.md` (#19180)</code></a></li>
<li><a
href="353266edf8"><code>docs:
fix a typo in `debug.md` (#19179)</code></a></li>
<li><a
href="3afb8a1dcf"><code>chore:
update dependency @eslint/json to ^0.8.0 (#19177)</code></a></li>
<li><a
href="5ff318a528"><code>docs:
delete unnecessary horizontal rule(`---`) in `nodejs-api`
(#19175)</code></a></li>
<li><a
href="576bcc5461"><code>docs:
mark more rules as handled by TypeScript (#19164)</code></a></li>
<li><a
href="1f77c53b12"><code>chore:
add `repository.directory` property to `package.json`
(#19165)</code></a></li>
<li><a
href="d46059410a"><code>chore:
update dependency @arethetypeswrong/cli to ^0.17.0
(#19147)</code></a></li>
<li><a
href="45cd4ead9c"><code>refactor:
update default options in rules (#19136)</code></a></li>
<li><a
href="742d054ac1"><code>docs:
note that `no-restricted-syntax` can be used with any language
(#19148)</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-09 16:50:02 +00:00
depfu[bot]
acdb406f4f
Update prettier 3.3.3 → 3.4.2 (minor) (#15564)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ prettier (3.3.3 → 3.4.2) ·
[Repo](https://github.com/prettier/prettier) ·
[Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/prettier/prettier/releases/tag/3.4.2">3.4.2</a></h4>

<blockquote><p dir="auto">🔗 <a
href="https://bounce.depfu.com/github.com/prettier/prettier/blob/main/CHANGELOG.md#342">Changelog</a></p></blockquote>
<h4><a
href="https://github.com/prettier/prettier/releases/tag/3.4.1">3.4.1</a></h4>

<blockquote><p dir="auto">🔗 <a
href="https://bounce.depfu.com/github.com/prettier/prettier/blob/main/CHANGELOG.md#341">Changelog</a></p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="52829385bc...cca946176c">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-09 17:27:12 +01:00
depfu[bot]
fd84f9d639
Update enhanced-resolve 5.17.1 → 5.18.0 (minor) (#15582)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ enhanced-resolve (5.17.1 → 5.18.0) ·
[Repo](https://github.com/webpack/enhanced-resolve)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/webpack/enhanced-resolve/releases/tag/v5.18.0">5.18.0</a></h4>

<blockquote><h3 dir="auto">Features</h3>
<ul dir="auto">
<li>Added wildcards support for aliases</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="247edebc90...27e457a905">See
the full diff on Github</a>. The new version differs by 9 commits:</p>
<ul>
<li><a
href="27e457a905"><code>chore(release):
5.18.0</code></a></li>
<li><a
href="88ceebe3cc"><code>feat:
add wildcards support for aliases</code></a></li>
<li><a
href="35b67ce834"><code>feat:
add wildcards</code></a></li>
<li><a
href="4fbcfa1c83"><code>chore(deps):
bump cross-spawn from 7.0.3 to 7.0.6</code></a></li>
<li><a
href="572a54f0c6"><code>chore(deps):
bump cross-spawn from 7.0.3 to 7.0.6</code></a></li>
<li><a
href="af4e2fb155"><code>ci:
add Node.js v23</code></a></li>
<li><a
href="bf443c04ac"><code>ci:
add Node.js v23</code></a></li>
<li><a
href="72999caf00"><code>chore(deps):
bump micromatch from 4.0.5 to 4.0.8</code></a></li>
<li><a
href="fbee162cc2"><code>chore(deps):
bump micromatch from 4.0.5 to 4.0.8</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-09 17:26:48 +01:00
Robin Malfait
fa2c83eb1e
Improve --theme() CSS function to only accept modern syntax (#15580)
This PR makes sure that the `--theme(…)` CSS function can only be used
with the modern syntax. For backwards compatibility, the `theme(…)`
function must be used with the older dot notation.
2025-01-09 16:25:25 +00:00
Robin Malfait
82589eb2c1
Migrate theme(…) to --theme(…), migrate calc(var(--spacing)*x) to --spacing(x) (#15579)
This PR improves the codemod tool to simplify 2 things:

1. Whenever you have a `theme(…)` call, we try to change it to a
`var(…)`, but if that doesn't work for some reason, we will make sure to
at least convert it to the more modern `--theme(…)`.
2. When converting `theme(spacing.2)`, we used to convert it to
`calc(var(--spacing)*2)`, but now we will convert it to `--spacing(2)`
instead.
2025-01-09 17:17:07 +01:00
Philipp Spiess
c766d7e274
Vite: Process <style> blocks inside Svelte files as a post-processor (#15436)
This PR changes the Svelte integration to be a post-processor similar to
what we're doing for `<style>` blocks in Astro and Vue files.

More details can be found in the GitHub discussion:
https://github.com/sveltejs/svelte/discussions/14668#discussioncomment-11620743
2025-01-09 17:16:33 +01:00
Philipp Spiess
a11c80d6c6
Upgrade lightningcss to 1.29.0 (#15576)
Closes #15438
Closes #15560
Closes #15561
Closes #15562

This PR upgrades `lightningcss` to `1.29.0` and uses the [new feature
flag](304389600f)
to disable the light-dark function transpilation.
2025-01-09 17:14:48 +01:00
Philipp Spiess
d7c8448eec
Remove all @keyframes in reference import mode (#15581)
This PR fixes an issue where JavaScript plugins were still able to
contribute `@keyframes` when loaded inside an `@reference` import. This
was possible because we only gated the `addBase` API and not the
`addUtilities` one which also has a special branch to handle `@keyframe`
rules.

To make this work, we have to create a new instance of the plugin API
that has awareness of wether the plugin accessing it is inside reference
import mode.

## Test plan

Added a unit test that reproduces the issue observed via #15544
2025-01-09 17:14:07 +01:00
Jordan Pittman
a3aec17908
Add musl binaries to the Standalone CLI (#15567)
Closes #15031

This adds musl binaries for the Standalone CLI on Linux aarch64 (ARM
64-bit) and x86_64 (Intel/AMD 64-bit).
2025-01-09 09:24:33 -05:00
Robin Malfait
76151d4293
Allow negative utility names in @utilty (#15573)
This PR fixes an issue where static utilities defined via `@utility`
wasn't possible if the name starts with `-`.

There are plenty of static utilities that start with `-`, but it wasn't
possible to register them via the `@utility` directive, only via the JS
API.

Example of a core utility that is now valid:
```css
@utility -inset-full {
  inset: -100%;
}
```
2025-01-08 21:44:02 +01:00
Robin Malfait
8d03db8178
Implement --spacing(…), --alpha(…) and --theme(…) CSS functions (#15572)
This PR implements new CSS functions that you can use in your CSS (or
even in arbitrary value position).

For starters, we renamed the `theme(…)` function to `--theme(…)`. The
legacy `theme(…)` function is still available for backwards
compatibility reasons, but this allows us to be future proof since
`--foo(…)` is the syntax the CSS spec recommends. See:
https://drafts.csswg.org/css-mixins/

In addition, this PR implements a new `--spacing(…)` function, this
allows you to write:

```css
@import "tailwindcss";

@theme {
  --spacing: 0.25rem;
}

.foo {
  margin: --spacing(4):
}
```

This is syntax sugar over:
```css
@import "tailwindcss";

@theme {
  --spacing: 0.25rem;
}

.foo {
  margin: calc(var(--spacing) * 4);
}
```

If your `@theme` uses the `inline` keyword, we will also make sure to
inline the value:

```css
@import "tailwindcss";

@theme inline {
  --spacing: 0.25rem;
}

.foo {
  margin: --spacing(4):
}
```

Boils down to:
```css
@import "tailwindcss";

@theme {
  --spacing: 0.25rem;
}

.foo {
  margin: calc(0.25rem * 4); /* And will be optimised to just 1rem */
}
```

---

Another new function function we added is the `--alpha(…)` function that
requires a value, and a number / percentage value. This allows you to
apply an alpha value to any color, but with a much shorter syntax:

```css
@import "tailwindcss";

.foo {
  color: --alpha(var(--color-red-500), 0.5);
}
```

This is syntax sugar over:
```css
@import "tailwindcss";

.foo {
  color: color-mix(in oklab, var(--color-red-500) 50%, transparent);
}
```

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Jonathan Reinink <jonathan@reinink.ca>
2025-01-08 20:16:08 +00:00
Robin Malfait
ee3add9d08
Add functional utility syntax (#15455)
This PR adds support for functional utilities constructed via CSS.

# Registering functional utilities in CSS

To register a functional utility in CSS, use the `@utility potato-*`
syntax, where the `-*` signals that this is a functional utility:

```css
@utility tab-* {
  tab-size: --value(--tab-size-*);
}
```

## Resolving values

The special `--value(…)` function is used to resolve the utility value.

### Resolving against `@theme` values

To resolve the value against a set of theme keys, use
`--value(--theme-key-*)`:

```css
@theme {
  --tab-size-1: 1;
  --tab-size-2: 2;
  --tab-size-4: 4;
  --tab-size-github: 8;
}

@utility tab-* {
  /* tab-1, tab-2, tab-4, tab-github */
  tab-size: --value(--tab-size-*);
}
```

### Bare values

To resolve the value as a bare value, use `--value({type})`, where
`{type}` is the data type you want to validate the bare value as:

```css
@utility tab-* {
  /* tab-1, tab-76, tab-971 */
  tab-size: --value(integer);
}
```

### Arbitrary values

To support arbitrary values, use `--value([{type}])` (notice the square
brackets) to tell Tailwind which types are supported as an arbitrary
value:

```css
@utility tab-* {
  /* tab-[1], tab-[76], tab-[971] */
  tab-size: --value([integer]);
}
```

### Supporting theme values, bare values, and arbitrary values together

All three forms of the `--value(…)` function can be used within a rule
as multiple declarations, and any declarations that fail to resolve will
be omitted in the output:

```css
@theme {
  --tab-size-github: 8;
}

@utility tab-* {
  tab-size: --value([integer]);
  tab-size: --value(integer);
  tab-size: --value(--tab-size-*);
}
```

This makes it possible to treat the value differently in each case if
necessary, for example translating a bare integer to a percentage:

```css
@utility opacity-* {
  opacity: --value([percentage]);
  opacity: calc(--value(integer) * 1%);
  opacity: --value(--opacity-*);
}
```

The `--value(…)` function can also take multiple arguments and resolve
them left to right if you don't need to treat the return value
differently in different cases:

```css
@theme {
  --tab-size-github: 8;
}

@utility tab-* {
  tab-size: --value(--tab-size-*, integer, [integer]);
}

@utility opacity-* {
  opacity: calc(--value(integer) * 1%);
  opacity: --value(--opacity-*, [percentage]);
}
```

### Negative values

To support negative values, register separate positive and negative
utilities into separate declarations:

```css
@utility inset-* {
  inset: calc(--var(--spacing) * --value([percentage], [length]));
}

@utility -inset-* {
  inset: calc(--var(--spacing) * --value([percentage], [length]) * -1);
}
```

## Modifiers

Modifiers are handled using the `--modifier(…)` function which works
exactly like the `--value(…)` function but operates on a modifier if
present:

```css
@utility text-* {
  font-size: --value(--font-size-*, [length]);
  line-height: --modifier(--line-height-*, [length], [*]);
}
```

If a modifier isn't present, any declaration depending on a modifier is
just not included in the output.

## Fractions

To handle fractions, we rely on the CSS `ratio` data type. If this is
used with `--value(…)`, it's a signal to Tailwind to treat the value +
modifier as a single value:

```css
/* The CSS `ratio` type is our signal to treat the value + modifier as a fraction */
@utility aspect-* {
  /* aspect-square, aspect-3/4, aspect-[7/9] */
  aspect-ratio: --value(--aspect-ratio-*, ratio, [ratio]);
}
```
2025-01-08 16:04:28 +01:00
Philipp Spiess
d6c4e72351
Add @reference "…" (#15565)
This PR adds a new `@reference "…"` API as an replacement for the
previously added [`@import "…"
reference`](https://github.com/tailwindlabs/tailwindcss/pull/15228). The
motivation for a distinct at rule is that `@import` is already handled
outside of Tailwind in some scenarios (e.g. when using in combination
with postcss-import, other pre-processors, or frameworks like Svelte).
While our implementation of hijacking the `media` attribute _works in
this cases_, it can cause annoying linter issues because tooling build
around `@import` does not know about our behavior.

To fix this, we've decided to move this mode into a separate at rule
that is passed-through in the other tooling. Here's an example of how
this would look like in Svelte:

```svelte
<h1>Hello world!</h1>

<style>
  @reference './theme.css';
  h1 {
    color: var(--theme-color);
  }
</style>
```

With this change, the Svelte linter would not be detecting unused CSS
from the `theme.css` file as it would if we'd rely on `@import`.
2025-01-07 15:41:43 +00:00
Robin Malfait
02cfc45057
Ensure @utility is processed before using them (#15542)
This PR fixes an issue where using an `@utility` before it is defined,
and _if_ that `@utility` contains `@apply`, that it won't result in the
expected output. But results in an `@apply` rule that is not
substituted. Additionally, if you have multiple levels of `@apply`, we
have to make sure that everything is applied (no pun intended) in the
right order.

Right now, the following steps are taken:

1. Collect all the `@utility` at-rules (and register them in the system
as utilities).
2. Substitute `@apply` on the AST (including `@utility`'s ASTs) with the
content of the utility.
3. Delete the `@utility` at-rules such that they are removed from the
CSS output itself.

The reason we do it in this order is because handling `@apply` during
`@utility`
handling means that we could rely on another `@utility` that is defined
later
and therefore the order of the utilities starts to matter. This is not a
bad
thing, but the moment you import multiple CSS files or plugins, this
could
become hard to manage.

Another important step is that when using `@utility foo`, the
implementation creates a `structuredClone` from its AST when first using
the utility. The reason we do that is because `foo` and `foo!` generate
different output and we don't want to accidentally mutate the same AST.
This structured clone is the start of the problem in the linked issue
(#15501).

If we don't do the structured clone, then substituting the `@apply`
rules would work, but then `foo` and `foo!` will generate the same
output, which is bad.

The linked issue has this structure:
```css
.foo {
  @apply bar;
}

@utility bar {
  @apply flex;
}
```

If we follow the steps above, this would substitute `@apply bar` first,
which
results in:

```css
.foo {
  @apply flex;
}
```

But the `bar` utility, was already cloned (and cached) so now we end up
with an `@apply` rule that is not substituted.

To properly solve this problem, we have to make sure that we collect all
the `@apply` at-rules, and apply them in the correct order. To do this,
we run a topological sort on them which ensures that all the
dependencies are applied before substituting the current `@apply`.

This means that in the above example, in order to process `@apply bar`,
we have to process the `bar` utility first.

If we run into a circular dependency, then we will throw an error like
before. You'll notice that the error message in this PR is updated to a
different spot. This one is a bit easier to grasp because it shows the
error where the circular dependency _starts_ not where it _ends_ (and
completes the circle). The previous message was not wrong (since it's a
circle), but now it's a bit easier to reason about.

Fixes: #15501
2025-01-07 15:19:37 +00:00
Jordan Pittman
dcf116bb30
Add @tailwindcss/browser package (#15558)
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-01-07 09:57:34 -05:00
Philipp Spiess
d4f693f7f3
Upgrade: Do not extract class names from functions (#15566)
This PR prevents the migration of utilities detected in function names,
e.g.: the use of `shadow` inside `filter: 'drop-shadow(…)'`.

## Test plan

Have content like this in a project you're migrating using the upgrade
tool:

```js
{
  filter: 'drop-shadow(0 0 0.5rem #000)'
}
```

This was verified by adding unit tests to the specific codemods and
adding an integration test.
2025-01-07 15:31:48 +01:00
depfu[bot]
c84acf8e82
Update turbo 2.2.3 → 2.3.3 (minor) (#15563) 2025-01-07 12:26:00 +01:00
Robin Malfait
af3345189a
Improve walk(…) performance (#15529)
This PR is a tiny improvement to the `walk(…)` implementations, not a
super big
deal but thought about something and was pleasently surprised that it
did have
an impact.

The idea is twofold:

1. Reduce array allocations while walking to build a `path` to the
current node. This re-uses the existing `path` array and pushes the
current node before the recursive call and pops it afterwards. This way
we don't need to allocate a new array for each recursive call. Testing
this on Tailwind UI means ~14k fewer allocations.
2. Instead of always calling `.splice(…)`, we can directly update a
single value in the array if we are replacing a node with another node.
   
Testing on the Tailwind UI codebase, this results in:

![image](https://github.com/user-attachments/assets/5a1c2102-1493-410f-b527-847fb4a75b31)

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-01-06 14:27:55 +00:00
depfu[bot]
8299d0486b
Update bun 1.1.40 → 1.1.42 (patch) (#15551)
Here is everything you need to know about this upgrade. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ bun (1.1.40 → 1.1.42) · [Repo](https://github.com/oven-sh/bun)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-06 12:45:16 +01:00
depfu[bot]
7f35af0619
Update @playwright/test 1.49.0 → 1.49.1 (patch) (#15550)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​playwright/test (1.49.0 → 1.49.1) ·
[Repo](https://github.com/Microsoft/playwright)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/microsoft/playwright/releases/tag/v1.49.1">1.49.1</a></h4>

<blockquote><h3 dir="auto">Highlights</h3>
<p dir="auto"><a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/33802">#33802</a>
- [Bug]: Codegen's Clear button doesn't work if not recording<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/33806">#33806</a>
- [Bug]: playwright hangs while waiting for pending navigations<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/33787">#33787</a>
- [Bug]: VSC extension isn't capturing all entered text<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/33788">#33788</a>
- [Regression]: Double clicking the steps in trace viewer doesn't filter
actions<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/33772">#33772</a>
- [Bug]: aria_snapshot generates invalid yaml when combined with an
aria-label attribut<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/33791">#33791</a>
- [Bug]: text input with number value raises "container is not iterable"
with to_match_aria_snapshot<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/33644">#33644</a>
- [Bug]: getByRole can't find element with the accessible name from
label element when aria-labelledby is not valid<br>
<a
href="https://bounce.depfu.com/github.com/microsoft/playwright/issues/33660">#33660</a>
- [Regression]: Unable to open Playwright UI in Dark Mode</p>
<h2 dir="auto">Browser Versions</h2>
<ul dir="auto">
<li>Chromium 131.0.6778.33</li>
<li>Mozilla Firefox 132.0</li>
<li>WebKit 18.2</li>
</ul>
<p dir="auto">This version was also tested against the following stable
channels:</p>
<ul dir="auto">
<li>Google Chrome 130</li>
<li>Microsoft Edge 130</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="a70a96ab25...88bc8afc78">See
the full diff on Github</a>. The new version differs by 16 commits:</p>
<ul>
<li><a
href="88bc8afc78"><code>chore:
mark v1.49.1 (#33921)</code></a></li>
<li><a
href="9e855d5b9a"><code>cherry-pick(#33821):
fix(recorder): allow clearing when recording is disabled
(#33900)</code></a></li>
<li><a
href="9365eb5dac"><code>cherry-pick(#33834):
fix: do not stall waiting for pending navigations after beforeunload
dismiss (#33856)</code></a></li>
<li><a
href="f92b2339fe"><code>cherry-pick(#33822):
fix(codegen): do not reset current tool upon clearing
highlight</code></a></li>
<li><a
href="008722b2d9"><code>cherry-pick(#33797):
fix(trace): in `indexTree` check `isVisible` before adding to result
(#33797)</code></a></li>
<li><a
href="1dc8b3cbdc"><code>cherry-pick(#33746):
chore: pin typescript while vue-tsc is broken</code></a></li>
<li><a
href="fbc770c804"><code>cherry-pick(#33793):
fix(aria): escape even more yaml (#33795)</code></a></li>
<li><a
href="1046fe0455"><code>cherry-pick(#33753):
docs: update extensions doc for new headless</code></a></li>
<li><a
href="1781bf35b3"><code>cherry-pick(#33706):
docs: release notes for languages v1.49</code></a></li>
<li><a
href="b52a21030f"><code>cherry-pick(#33712):
docs(python): add
LocatorAssertions.NotToMatchAriaSnapshot</code></a></li>
<li><a
href="2128fac196"><code>cherry-pick(#33693):
docs: add video for 1.49</code></a></li>
<li><a
href="2ba644852b"><code>cherry-pick(#33668):
docs(aria): add demo video</code></a></li>
<li><a
href="4b0eca4d22"><code>cherry-pick(#33680):
docs: add docs for 1.49.0&#39;s new &quot;chromium&quot;
option</code></a></li>
<li><a
href="e3c5986c5b"><code>cherry-pick(#33686):
chore: escape more yaml values</code></a></li>
<li><a
href="b3aaee0248"><code>cherry-pick(#33667):
fix(role): ignore invalid `aria-labelledby` attributes
(#33672)</code></a></li>
<li><a
href="120cdf664b"><code>cherry-pick(#33662):
fix: dark-mode in UI Mode</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-06 11:33:25 +00:00
Robin Malfait
52325a8cf8
Only compile arbitrary values ending in ] (#15503)
This PR ensures that if you are using an arbitrary value such as
`bg-[red` that it only compiles if it's a properly closed arbitrary
value.

Currently what happens is that it assumes the `]` is there, and cuts it
off. This then results in the `bg-[red` to be compiled as:

```css
.bg-\[red {
  background-color: re;
}
```

Note how the `d` in `red` is cut off. That's the assumption that the `]`
is there.

This PR fixes that by ensuring that the arbitrary value is properly
closed.

Fixes: #15484
2025-01-06 12:00:36 +01:00
Robin Malfait
9075db0c10
Apply Clippy suggestions (#15549)
While working on other PRs, I noticed that Clippy had some suggestions
(warnings). This PR fixes those warnings.
2025-01-06 11:59:31 +01:00
depfu[bot]
427efa21b9
Update jiti 2.4.0 → 2.4.2 (patch) (#15491)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ jiti (2.4.0 → 2.4.2) · [Repo](https://github.com/unjs/jiti) ·
[Changelog](https://github.com/unjs/jiti/blob/main/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/unjs/jiti/releases/tag/v2.4.2">2.4.2</a></h4>

<blockquote><p dir="auto"><a
href="https://bounce.depfu.com/github.com/unjs/jiti/compare/v2.4.1...v2.4.2">compare
changes</a> (📦 bundled dependencies updated)</p>
<h3 dir="auto">🩹 Fixes</h3>
<ul dir="auto">
<li>
<strong>cache:</strong> Add <code class="notranslate">+map</code> suffix
to fs entries when <code class="notranslate">sourceMaps</code> enabled
(<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/352">#352</a>)</li>
<li>Use native require cache of loaded entries only for Node.js 22.12.+
compatibility (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/348">#348</a>)</li>
</ul></blockquote>
<h4><a
href="https://github.com/unjs/jiti/releases/tag/v2.4.1">2.4.1</a></h4>

<blockquote><p dir="auto"><a
href="https://bounce.depfu.com/github.com/unjs/jiti/compare/v2.4.0...v2.4.1">compare
changes</a></p>
<h3 dir="auto">🩹 Fixes</h3>
<ul dir="auto">
<li>Interop modules with primitive default export (<a
href="https://bounce.depfu.com/github.com/unjs/jiti/pull/343">#343</a>)</li>
</ul></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="2f9c2376e7...340e2a733c">See
the full diff on Github</a>. The new version differs by 9 commits:</p>
<ul>
<li><a
href="340e2a733c"><code>chore(release):
v2.4.2</code></a></li>
<li><a
href="cf952e4573"><code>fix:
use native require cache of loaded entries only (#348)</code></a></li>
<li><a
href="4dacbf1a7c"><code>fix(cache):
add `+map` suffix to fs entries when `sourceMaps` enabled
(#352)</code></a></li>
<li><a
href="0c2c0d0d5c"><code>test:
simplify snapshot tests (#351)</code></a></li>
<li><a
href="7b7ffefdde"><code>chore:
update deps</code></a></li>
<li><a
href="36d52d59c9"><code>chore(deps):
update all non-major dependencies (#344)</code></a></li>
<li><a
href="ad6191f046"><code>chore(release):
v2.4.1</code></a></li>
<li><a
href="cca319bbcd"><code>fix:
interop modules with primitive default export (#343)</code></a></li>
<li><a
href="58d3f5f8b8"><code>chore:
update deps</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-06 11:42:53 +01:00
depfu[bot]
cd15caa846
🚨 [security] Update all of nextjs 15.1.0 → 15.1.3 (patch) (#15535)
<hr>

🚨 <b>Your current dependencies have known security vulnerabilities</b> 🚨

This dependency update fixes known security vulnerabilities. Please see
the details below and assess their impact carefully. We recommend to
merge and deploy this as soon as possible!
<hr>



Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint-config-next (15.1.0 → 15.1.3)





Sorry, we couldn't find anything useful about this release.



#### ✳️ next (15.1.0 → 15.1.3) ·
[Repo](https://github.com/vercel/next.js)


<details>

<summary>Security Advisories 🚨</summary>
<h4><a
href="https://bounce.depfu.com/github.com/vercel/next.js/security/advisories/GHSA-7m27-7ghc-44w9">🚨
Next.js Allows a Denial of Service (DoS) with Server Actions</a></h4>
<blockquote><h3 dir="auto">Impact</h3>
<p dir="auto">A Denial of Service (DoS) attack allows attackers to
construct requests that leaves requests to Server Actions hanging until
the hosting provider cancels the function execution.</p>
<p dir="auto"><em>Note: Next.js server is idle during that time and only
keeps the connection open. CPU and memory footprint are low during that
time.</em></p>
<p dir="auto">Deployments without any protection against long running
Server Action invocations are especially vulnerable. Hosting providers
like Vercel or Netlify set a default maximum duration on function
execution to reduce the risk of excessive billing.</p>
<p dir="auto">This is the same issue as if the incoming HTTP request has
an invalid <code class="notranslate">Content-Length</code> header or
never closes. If the host has no other mitigations to those then this
vulnerability is novel.</p>
<p dir="auto">This vulnerability affects only Next.js deployments using
Server Actions.</p>
<h3 dir="auto">Patches</h3>
<p dir="auto">This vulnerability was resolved in Next.js 14.2.21,
15.1.2, and 13.5.8. We recommend that users upgrade to a safe
version.</p>
<h3 dir="auto">Workarounds</h3>
<p dir="auto">There are no official workarounds for this
vulnerability.</p>
<h3 dir="auto">Credits</h3>
<p dir="auto">Thanks to the PackDraw team for responsibly disclosing
this vulnerability.</p></blockquote>
</details>

<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.1.3">15.1.3</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon
octicon-info mr-2" viewbox="0 0 16 16" version="1.1" width="16"
height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0
8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1
7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1
0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0
2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>Retry manifest file loading only in dev mode: <a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/73900">#73900</a>
</li>
<li>Use shared worker for lint &amp; typecheck steps: <a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/74154">#74154</a>
</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to <a
href="https://bounce.depfu.com/github.com/unstubbable">@unstubbable</a>
and <a href="https://bounce.depfu.com/github.com/ztanner">@ztanner</a>
for helping!</p></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.1.2">15.1.2</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon
octicon-info mr-2" viewbox="0 0 16 16" version="1.1" width="16"
height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0
8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1
7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1
0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0
2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>Update React from 7283a213-20241206 to 65e06cb7-20241218: <a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/74117">#74117</a>
</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to <a
href="https://bounce.depfu.com/github.com/ztanner">@ztanner</a> for
helping!</p></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.1.1">15.1.1</a></h4>

<blockquote><div class="markdown-alert markdown-alert-note" dir="auto">
<p class="markdown-alert-title" dir="auto"><svg class="octicon
octicon-info mr-2" viewbox="0 0 16 16" version="1.1" width="16"
height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0
8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1
7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1
0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0
2Z"></path></svg>Note</p>
<p dir="auto">This release is backporting bug fixes. It does
<strong>not</strong> include all pending features/changes on canary.</p>
</div>
<h3 dir="auto">Core Changes</h3>
<ul dir="auto">
<li>fix(turbo): sassOptions silenceDeprecations was not overwritten with
user options: <a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/73937">#73937</a>
</li>
<li>refactor collectAppPageSegments: <a
href="https://bounce.depfu.com/github.com/vercel/next.js/pull/73908">#73908</a>
</li>
</ul>
<h3 dir="auto">Credits</h3>
<p dir="auto">Huge thanks to <a
href="https://bounce.depfu.com/github.com/devjiwonchoi">@devjiwonchoi</a>
and <a href="https://bounce.depfu.com/github.com/ztanner">@ztanner</a>
for helping!</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="dafcd43fac...4cbaaa118d">See
the full diff on Github</a>. The new version differs by 11 commits:</p>
<ul>
<li><a
href="4cbaaa118d"><code>v15.1.3</code></a></li>
<li><a
href="221d18ba18"><code>Backport
v15: used shared worker for lint &amp; typecheck steps (#74154)
(#74285)</code></a></li>
<li><a
href="7d880a3b5b"><code>Backport
v15: Retry manifest file loading only in dev mode (#73900)
(#74283)</code></a></li>
<li><a
href="df392a1b97"><code>v15.1.2</code></a></li>
<li><a
href="40c9424beb"><code>Backport
(v15): Update React from 7283a213-20241206 to 65e06cb7-20241218
(#74117)</code></a></li>
<li><a
href="4384c6834a"><code>v15.1.1</code></a></li>
<li><a
href="d137863475"><code>run
build_and_test workflow on backport branch</code></a></li>
<li><a
href="d27bb14b68"><code>backport:
fix(turbo): sassOptions silenceDeprecations was not overwritten with
user options (#74005)</code></a></li>
<li><a
href="0c8187a312"><code>Add
NEXT_PRIVATE_SKIP_CANARY_CHECK env for bench job
(#73763)</code></a></li>
<li><a
href="e83ab18c4c"><code>backport:
refactor collectAppPageSegments (#73996)</code></a></li>
<li><a
href="ada25fc25e"><code>Designate
as backport branch</code></a></li>
</ul>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-06 10:23:13 +00:00
depfu[bot]
bca432ed99
Update all of nextjs 15.0.3 → 15.1.0 (minor) (#15536)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ eslint-config-next (15.0.3 → 15.1.0)





Sorry, we couldn't find anything useful about this release.



#### ✳️ next (15.0.3 → 15.1.0) ·
[Repo](https://github.com/vercel/next.js)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.1.0">15.1.0</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<h4><a
href="https://github.com/vercel/next.js/releases/tag/v15.0.4">15.0.4</a></h4>

<blockquote><em>More info than we can show here.</em></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please let us
know.</a></em></p>
</details>

<details>
<summary>Commits</summary>
<p><a
href="7dc7be201a...dafcd43fac">See
the full diff on Github</a>. The new version differs by more commits
than we can show here.</p>
</details>












---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2025-01-06 11:18:39 +01:00
Kid
608b1399cd
Fix inset-shadow-* suggestions in IntelliSense (#15471) 2024-12-22 09:13:48 -05:00
Robin Malfait
36dfe1f574
Rename bg-round -> bg-repeat-round, bg-space -> bg-repeat-space (#15462)
When we were porting all of the utilities from v3 to v4 we accidentally
used the wrong names for these two classes, so this PR fixes that and
corrects them back to the names used in v3.

| Before | After |
| --- | --- |
| `bg-round` | `bg-repeat-round` |
| `bg-space` | `bg-repeat-space` |

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-12-20 09:54:40 -05:00
Jordan Pittman
00ccbdc937
Don’t detect arbitrary properties when preceded by an escape (#15456)
This is a targeted bug fix uncovered by the v4 docs.

Given this code:
```html
<!-- [!code word:group-has-\\[a\\]\\:block] -->
```

We'd pick up `[a\\]\\:block]` as a candidate which would then make it
far enough to get output to CSS and throw an error. This makes sure we
don't try to start an arbitrary property if the preceding character is a
`\`

cc @RobinMalfait this look okay?

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-12-20 15:22:49 +01:00
depfu[bot]
34340e3b82
Update @vitejs/plugin-react 4.3.1 → 4.3.4 (patch) (#15453)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​vitejs/plugin-react (4.3.1 → 4.3.4) ·
[Repo](https://github.com/vitejs/vite) ·
[Changelog](https://github.com/vitejs/vite/blob/main/packages/plugin-react/CHANGELOG.md)



<details>
<summary>Release Notes</summary>
<h4><a
href="https://github.com/vitejs/vite/releases/tag/v4.3.4">4.3.4</a></h4>

<blockquote><p dir="auto">Please refer to <a
href="https://bounce.depfu.com/github.com/vitejs/vite/blob/v4.3.4/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p></blockquote>
<h4><a
href="https://github.com/vitejs/vite/releases/tag/v4.3.3">4.3.3</a></h4>

<blockquote><p dir="auto">Please refer to <a
href="https://bounce.depfu.com/github.com/vitejs/vite/blob/v4.3.3/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p></blockquote>
<h4><a
href="https://github.com/vitejs/vite/releases/tag/v4.3.2">4.3.2</a></h4>

<blockquote><p dir="auto">Please refer to <a
href="https://bounce.depfu.com/github.com/vitejs/vite/blob/v4.3.2/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p></blockquote>
<p><em>Does any of this look wrong? <a
href="feedback">Please
let us know.</a></em></p>
</details>













---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2024-12-19 16:38:05 +01:00
depfu[bot]
5f95c5e43a
Update @types/bun 1.1.13 → 1.1.14 (patch) (#15451)
Here is everything you need to know about this update. Please take a
good look at what changed and the test results before merging this pull
request.

### What changed?




#### ✳️ @​types/bun (1.1.13 → 1.1.14) ·
[Repo](https://github.com/DefinitelyTyped/DefinitelyTyped)





Sorry, we couldn't find anything useful about this release.











---
![Depfu
Status](https://depfu.com/badges/edd6acd35d74c8d41cbb540c30442adf/stats.svg)

[Depfu](https://depfu.com) will automatically keep this PR
conflict-free, as long as you don't add any commits to this branch
yourself. You can also trigger a rebase manually by commenting with
`@depfu rebase`.

<details><summary>All Depfu comment commands</summary>
<blockquote><dl>
<dt>@​depfu rebase</dt><dd>Rebases against your default branch and
redoes this update</dd>
<dt>@​depfu recreate</dt><dd>Recreates this PR, overwriting any edits
that you've made to it</dd>
<dt>@​depfu merge</dt><dd>Merges this PR once your tests are passing and
conflicts are resolved</dd>
<dt>@​depfu cancel merge</dt><dd>Cancels automatic merging of this
PR</dd>
<dt>@​depfu close</dt><dd>Closes this PR and deletes the branch</dd>
<dt>@​depfu reopen</dt><dd>Restores the branch and reopens this PR (if
it's closed)</dd>
<dt>@​depfu pause</dt><dd>Ignores all future updates for this dependency
and closes this PR</dd>
<dt>@​depfu pause [minor|major]</dt><dd>Ignores all future minor/major
updates for this dependency and closes this PR</dd>
<dt>@​depfu resume</dt><dd>Future versions of this dependency will
create PRs again (leaves this PR as is)</dd>
</dl></blockquote>
</details>

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2024-12-19 16:19:58 +01:00
depfu[bot]
0544c2a341 Update bun to version 1.1.40 2024-12-19 12:57:27 +00:00
Jordan Pittman
ab49b59556
Fixed definition of place-content-* (#15440)
The utilities `place-content-between`, `place-content-around`, and
`place-content-evenly` are defined incorrectly. This PR fixes them.

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-12-18 20:17:29 -05:00
Jordan Pittman
7bf11f9087
Move --container-prose to --max-width-prose (#15439)
We don’t want utilities like `basis-prose`, `w-prose`, etc existing nor
a `@prose:*` variant. So we’re moving the theme key to `--max-width-*`
to align with the definition as it was in v3.

cc @adamwathan

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2024-12-18 20:15:37 -05:00
Robin Malfait
c9dfe17cac
Prepare v4.0.0-beta.8 release (#15418) 2024-12-17 13:31:28 +01:00
Robin Malfait
352d1b9fcf
Ensure Symbol.dispose and Symbol.asyncDispose are available (#15404)
We recently introduced some better instrumentation
(https://github.com/tailwindlabs/tailwindcss/pull/15303) which uses the
new `using` keyword. I made sure that this was compiled correctly for
environments where `using` is not available yet.

The issue is that this also relies on `Symbol.dispose` being available.
In my testing on our minimal required Node.js version (18) it did work
fine. However, turns out that I was using `18.20.x` locally where
`Symbol.dispose` **_is_** available, but on older version of Node.js 18
(e.g.: `18.17.x`) it is **_not_** available. This now results in some
completely broken builds, e.g.: when running on Cloudflare Pages. See:
#15399

I could reproduce this error in CI, by temporarily downgrading the used
Node.js version to `18.17.0`. See:

<img width="1142" alt="image"
src="https://github.com/user-attachments/assets/5bf30f80-9ca0-40d9-ad02-d1ffb4e0e5dd"
/>

Implementing the proper polyfill, as recommended by the TypeScript docs
( see:
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#:~:text=Symbol.dispose,-??=%20Symbol(%22Symbol.dispose
), the error goes away. (If you look at CI after the polyfill, it still
fails but for different reasons unrelated to this change)

Fixes: #15399

---

## Test plan

1. I reproduced it in CI, and I kept the commits so that you can take a
look where it fails with the `Object not disposable`.
2. Using the provided reproduction from #15399:

### Before

It works on Node.js v18.20.x, but switching to Node.js v18.17.x you can
see it fail:

<img width="1607" alt="image"
src="https://github.com/user-attachments/assets/cb6ab73a-8eb2-4003-bab7-b2390f1c879d"
/>

### After

Using pnpm's overrides, we can apply the fix from this PR and test it in
the reproduction. You'll notice that it now works in both Node.js
v18.20.x and v18.17.x

<img width="1604" alt="image"
src="https://github.com/user-attachments/assets/b3a65557-0658-4cb0-a2f9-e3079c7936d5"
/>
2024-12-16 14:17:44 +01:00
Robin Malfait
a39d03663e
use single quotes 2024-12-13 15:30:58 +01:00
Robin Malfait
0072f01376
Prepare v4.0.0-beta.7 release (#15392)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-12-13 14:18:21 +00:00
Philipp Spiess
04dcf27de5
Change Chrome target to 111 (#15389)
Resolves https://github.com/tailwindlabs/tailwindcss/discussions/15387

This PR changes the Chrome target to 111. We initially picked 120
because of the unnecessary `:dir()` down-leveling but we that was maybe
a bit too recent as it was causing some necessary prefixes to not be
generated (e.g. `-webkit-background-clip`).

This PR changes it to 111 which we require for the `color-mix()`
function. To work around the `:dir()` down-leveling we also disable the
`DirSelector` lightningcss feature which is used to control this
behavior:
https://sourcegraph.com/github.com/parcel-bundler/lightningcss/-/blob/src/selector.rs?L1964-1965
2024-12-13 15:07:04 +01:00
Robin Malfait
4277857702
Use better names for CI workflow, similar to Integration Tests workflow (#15378)
This PR improves the CI workflow names such that they are a bit more
pretty.

E.g.:
```diff
- CI / tests (20, namespace-profile-default, true)
+ CI / Linux
```
2024-12-12 13:58:56 +01:00
Robin Malfait
2a29c29441
Improve integration tests (stability + performance) (#15125)
This PR improves the integration tests in two ways:
1. Make the integration tests more reliable and thus less flakey
2. Make the integration tests faster (by introducing concurrency)

Tried a lot of different things to make sure that these tests are fast
and stable.

---

The biggest issue we noticed is that some tests are flakey, these are
tests with long running dev-mode processes where watchers are being used
and/or dev servers are created.
To solve this, all the tests that spawn a process look at stdout/stderr
and wait for a message from the process to know whether we can start
making changes.

For example, in case of an Astro project, you get a `watching for file
changes` message. In case of Nuxt project you can wait for an `server
warmed up in` and in case of Next.js there is a `Ready in` message.

These depend on the tools being used, so this is hardcoded per test
instead of a magically automatic solution.

These messages allow us to wait until all the initial necessary work,
internal watchers and/or dev servers are setup before we start making
changes to the files and/or request CSS stylesheets before the server(s)
are ready.

---

Another improvement is how we setup the dev servers. Before, we used to
try and get a free port on the system and use a `--port` flag or a
`PORT` environment variable. Instead of doing this (which is slow), we
rely on the process itself to show a URL with a port. Basically all
tools will try to find a free port if the default port is in use. We can
then use the stdout/stderr messages to get the URL and the port to use.

To reduce the amount of potential conflicts in ports, we used to run
every test and every file sequentially to basically guarantee that ports
are free. With this new approach where we rely on the process, I noticed
that we don't really run into this issue again (I reran the tests
multiple times and they were always stable)

<img width="316" alt="image"
src="https://github.com/user-attachments/assets/b75ddab4-f919-4995-85d0-f212b603e5c2"
/>
Note: these tests run Linux, Windows and macOS in this branch just for
testing purposes. Once this is done, we will only run Linux tests on PRs
and run all 3 of them on the `next` branch.

We do make the tests concurrent by default now, which in theory means
that there could be conflicts (which in practice means that the process
has to do a few more tries to find a free port). To reduce these
conflicts, we split up the integration tests such that Vite, PostCSS,
CLI, … tests all run in a separate job in the GitHub actions workflow.

<img width="312" alt="image"
src="https://github.com/user-attachments/assets/fe9a58a1-98eb-4d9b-8845-a7c8a7af5766"
/>

Comparing this branch against the `next` branch, this is what CI looks
like right now:

| `next` | `feat/improve-integration-tests` |
| --- | --- |
| <img width="594" alt="image"
src="https://github.com/user-attachments/assets/540d21eb-ab03-42e8-9f6f-b3a071fc7635"
/> | <img width="672" alt="image"
src="https://github.com/user-attachments/assets/8ef2e891-08a1-464b-9954-4153174ebce7"
/> |

There also was a point in time where I introduced sequential tests such
that all spawned processes still run after each other, but so far I
didn't run into issues if we keep them concurrent so I dropped that
code.

Some small changes I made to make things more reliable:
1. When relying on stdout/stderr messages, we split lines on `\n` and we
strip all the ANSI escapes which allows us to not worry about special
ANSI characters when finding the URL or a specific message to wait for.
2. Once a test is done, we `child.kill()` the spawned process. If that
doesn't work, for whatever reason, we run a `child.kill('SIGKILL')` to
force kill the process. This could technically lead to some memory or
files not being cleaned up properly, but once CI is done, everything is
thrown away anyway.
3. As you can see in the screenshots, I used some nicer names for the
workflows.

| `next` | `feat/improve-integration-tests` |
| --- | --- |
| <img width="276" alt="image"
src="https://github.com/user-attachments/assets/e574bb53-e21b-4619-9cdb-515431b255b9"
/> | <img width="179" alt="image"
src="https://github.com/user-attachments/assets/8bc75119-fb91-4500-a1d0-bd09f74c93ad"
/> |

They also look a bit nicer in the PR overview as well:
<img width="929" alt="image"
src="https://github.com/user-attachments/assets/04fc71fc-74b0-4e7c-9047-2aada664efef"
/>

The very last commit just filters out Windows and macOS tests again for
PRs (but they are executed on the `next` branch.

---

### Nest steps

I think for now we are in a pretty good state, but there are some things
we can do to further improve everything (mainly make things faster) but
aren't necessary. I also ran into issue while trying it so there is more
work to do.

1. More splits — instead of having a Vite folder and PostCSS folder, we
can go a step further and have folders for Next.js, Astro, Nuxt, Remix,
…
2. Caching — right now we have to run the build step for every OS on
every "job". We can re-use the work here by introducing a setup job that
the other jobs rely on. @thecrypticace and I tried it already, but were
running into some Bun specific Standalone CLI issues when doing that.
3. Remote caching — we could re-enable remote caching such that the
`build` step can be full turbo (e.g.: after a PR is merged in `next` and
we run everything again)
2024-12-12 13:48:56 +01:00
Robin Malfait
bcf70990a7
Improve debug logs (#15303)
This PR improves the debug logs for the `@tailwindcss/postcss`
integration. It uses custom instrumentation to provide a nested but
detailed overview of where time is spent during the build process.

The updated logs look like this:
```
[0.15ms] [@tailwindcss/postcss] src/app/geistsans_9fc57718.module.css
[0.14ms]   ↳ Quick bail check
[0.02ms] [@tailwindcss/postcss] src/app/geistsans_9fc57718.module.css
[0.01ms]   ↳ Quick bail check

[0.03ms] [@tailwindcss/postcss] src/app/geistmono_b9f59162.module.css
[0.02ms]   ↳ Quick bail check
[0.12ms] [@tailwindcss/postcss] src/app/geistmono_b9f59162.module.css
[0.11ms]   ↳ Quick bail check

[42.09ms] [@tailwindcss/postcss] src/app/globals.css
[ 0.01ms]   ↳ Quick bail check
[12.12ms]   ↳ Setup compiler
[ 0.11ms]     ↳ PostCSS AST -> Tailwind CSS AST
[11.99ms]     ↳ Create compiler
[ 0.07ms]   ↳ Register full rebuild paths
[ 0.06ms]   ↳ Setup scanner
[ 7.51ms]   ↳ Scan for candidates
[ 5.86ms]   ↳ Register dependency messages
[ 5.88ms]   ↳ Build utilities
[ 8.34ms]   ↳ Optimization
[ 0.23ms]     ↳ AST -> CSS
[ 4.20ms]     ↳ Lightning CSS
[ 3.89ms]     ↳ CSS -> PostCSS AST
[ 1.97ms]   ↳ Update PostCSS AST
```
2024-12-11 15:27:20 +01:00
Jonathan Reinink
2cbc915193
Rename --aspect-ratio-* theme key to --aspect-* (#15365)
This PR renames the `--aspect-ratio` theme key to `--aspect`. This is to
match what we've done with other theme keys where they match the utility
names, like `--ease` and `--leading`.

```diff
  @theme {
-   --aspect-ratio-retro: 4 / 3;
+   --aspect-retro: 4 / 3;
  }
```

Additionally, I've also converted the existing `aspect-video` static
utility to a theme value. This will allow people to override this
utility in their own projects—something that's not possible with static
utilities. This change feels appropriate since the video aspect ratio is
subjective, unlike other static utilities like `aspect-square`.

```css
@theme {
  --aspect-video: 4 / 3; /* N64 baby! */
}
```
2024-12-10 14:33:20 -05:00
Jordan Pittman
ea9d700cf9
Optimize AST before printing for IntelliSense (#15347)
We changed how AST printing worked recently but forgot to update this.
Which causes us to print properties with a value of `undefined` in
Tailwind Play, Tailwind CSS IntelliSense, and our Language Server
(oops).

This PR fixes this by optimizing the AST before printing (which is what
toCss did previously)
2024-12-09 12:44:42 -05:00
Robin Malfait
d0b0375d00
Alias @tailwindcss/upgrade to the latest tag (#15308)
When releasing a new beta version, we publish everything to a `next`
tag, this is important so that you can still use `npm install
tailwindcss` and get the current v3 instead of the beta v4 version.

However, some packages don't have a meaning before the v4 release. This
PR aliases the `next` tag to the `latest` tag for the following
packages:

- `@tailwindcss/upgrade`

This in turn allow you to run `npx @tailwindcss/upgrade` for example,
instead of using `npx @tailwindcss/upgrade@next`.

---

> [!NOTE]
> I actually have no idea how to properly test this without actually
running it in CI. The `npm dist-tag` command doesn't have a `--dry-run`
flag. Additionally, when running this command locally we have to
authenticate (obviously) and in CI we typically don't have to do this
because of the `NODE_AUTH_TOKEN` (at least that's the case when running
`npm publish`) so I'm hoping this Just Works™ as expected.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-12-09 13:11:41 +01:00
Philipp Spiess
1566c3a17b
Suggest shadow-none (#15342)
Noticed a missing `shadow-none` completion while finally starting the
work to migrate my personal blog to Tailwind, lol.
2024-12-09 12:32:50 +01:00
Robin Malfait
d444362d5e
Skip creating a compiler for CSS files that should not be processed (#15340)
This PR skips creating a compiler in the `@tailwindcss/postcss`
implementation if we know that the CSS file we are handling is
definitely not a Tailwind CSS file.

This is a performance improvement for initial builds where some CSS
files would've been handling by Tailwind CSS but shouldn't. E.g.: When
setting up custom fonts in Next.js applications, each font will have
it's own CSS file that is passed to `@tailwindcss/postcss`.

Since they don't contain `@import` or any other Tailwind CSS directives,
we can just skip them.
2024-12-09 11:55:33 +01:00
Philipp Spiess
a7ebc9cda1
Expose flattenColorPalette (#15318)
Resolves #15315 

It looks like we implemented this in Core but forgot to expose it from
the distributed package (the references are only used for testing
plugins internally right now). This exposes `flattenColorPalette` under
the old import path of `tailwindcss/lib/util/flattenColorPalette`.

## Test Plan

Added the following plugin to the Vite example and ensured it works as
expected:

```ts
import flattenColorPalette from 'tailwindcss/lib/util/flattenColorPalette'
import plugin from 'tailwindcss/plugin'

export default plugin(({ matchUtilities, theme }) => {
  matchUtilities(
    {
      'hover-bg': (value) => {
        return {
          '&:hover': {
            backgroundColor: value,
          },
        }
      },
    },
    { values: flattenColorPalette(theme('colors')) },
  )
})
```

<img width="462" alt="Screenshot 2024-12-06 at 11 47 44"
src="https://github.com/user-attachments/assets/11163390-053e-4c6e-8cb9-ae67184ad594">
2024-12-09 11:33:19 +01:00
Philipp Spiess
94a3cff687
Fix PostCSS watcher warnings on Windows (#15321)
Resolves #15320
Resolves #15175

Turns out that the postcss file watcher does not like our Unix based
paths and will print a warning about them. This fixes the issue by
calling `path.resolve()` to convert it back to a Windows-style absolute
path if necessary.

## Test Plan

Tested on Windows with a new Next.js 14 project. Ensured that file
reloads also still work (changes to the `tsx` file are picked up
correctly). Also ensure that the CI runs on Windows.

### Before

<img width="1178" alt="Screenshot 2024-12-06 at 13 12 23"
src="https://github.com/user-attachments/assets/70c1fe45-6983-4fb4-9889-716a0cbef03a">

### After

<img width="1196" alt="Screenshot 2024-12-06 at 13 23 24"
src="https://github.com/user-attachments/assets/0b9e3ff7-c5b6-4ccb-85a9-e7ba7aee355a">
2024-12-09 10:44:25 +01:00
Jordan Pittman
3d0b86c7d2
Prepare v4.0.0-beta.6 release (#15325) 2024-12-06 14:32:21 -05:00
Philipp Spiess
1238d07ca2
Reference imports should not generate utilities (#15307)
We noticed an issue where the new `@import "…" reference` syntax was not
throwing away `@tailwind` declarations, effectively causing you to
create utility classes whenever you used this feature.

This is especially noticed in setups with very strict compilers like
Svelte.

## Test Plan

### Before

<img width="1142" alt="Screenshot 2024-12-05 at 11 56 00"
src="https://github.com/user-attachments/assets/546f0eb3-4401-48c9-9268-76992e899226">

### After

<img width="2560" alt="Screenshot 2024-12-05 at 12 27 30"
src="https://github.com/user-attachments/assets/16732000-e02c-49bc-ac6f-91da0cfcc7e8">
2024-12-05 16:18:06 +01:00
Philipp Spiess
85da88f851
Prepare v4.0.0-beta.5 (#15285)
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-12-04 16:28:16 +01:00
Robin Malfait
408fa99849
Use AST transformations in @tailwindcss/postcss (#15297)
This PR improves the `@tailwindcss/postcss` integration by using direct
AST transformations between our own AST and PostCSS's AST. This allows
us to skip a step where we convert our AST into a string, then parse it
back into a PostCSS AST.

The only downside is that we still have to print the AST into a string
if we want to optimize the CSS using Lightning CSS. Luckily this only
happens in production (`NODE_ENV=production`).

This also introduces a new private `compileAst` API, that allows us to
accept an AST as the input. This allows us to skip the PostCSS AST ->
string -> parse into our own AST step.

To summarize:

Instead of:
- Input: `PostCSS AST` -> `.toString()` -> `CSS.parse(…)` -> `Tailwind
CSS AST`
- Output: `Tailwind CSS AST` -> `toCSS(ast)` -> `postcss.parse(…)` ->
`PostCSS AST`

We will now do this instead:
- Input: `PostCSS AST` -> `transform(…)` -> `Tailwind CSS AST`
- Output: `Tailwind CSS AST` -> `transform(…)` -> `PostCSS AST`


---

Running this on Catalyst, the time spent in the `@tailwindcss/postcss`
looks like this:
- Before: median time per run: 19.407687 ms
- After: median time per run: 11.8796455 ms

This is tested on Catalyst which roughly generates ~208kb worth of CSS
in dev mode.

While it's not a lot, skipping the stringification and parsing seems to
improve this step by ~40%.

Note: these times exclude scanning the actual candidates and only time
the work needed for parsing/stringifying the CSS from and into ASTs. The
actual numbers are a bit higher because of the Oxide scanner reading
files from disk. But since that part is going to be there no matter
what, it's not fair to include it in this benchmark.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-12-04 15:43:59 +01:00
Jordan Pittman
536e11895e
Add some missing suggestions in IntelliSense (#15288)
When we renamed the theme keys this got missed

Theme keys like `--font-sans` will result in suggestions for `font-sans`
again
2024-12-03 19:11:30 -05:00
Philipp Spiess
78f5b087d4
Add @import "…" reference (#15228)
Closes #15219

This PR adds a new feature, `@import "…" reference` that can be used to
load Tailwind CSS configuration files without adding any style rules to
the CSS.

The idea is that you can use this in combination with your Tailwind CSS
root file when you need to have access to your full CSS config outside
of the main stylesheet. A common example is for Vue, Svelte, or CSS
modules:

```css
@import "./tailwind.css" reference;

.link {
  @apply underline;
}
```

Importing a file as a reference will convert all `@theme` block to be
`reference`, so no CSS variables will be emitted. Furthermore it will
strip out all custom styles from the stylesheet. Furthermore plugins
registered via `@plugin` or `@config` inside reference-mode files will
not add any content to the CSS file via `addBase()`.

## Test Plan

Added unit test for when we handle the import resolution and when
`postcss-import` does it outside of Tailwind CSS. I also changed the
Svelte and Vue integration tests to use this new syntax to ensure it
works end to end.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-12-03 14:15:24 +01:00
Philipp Spiess
3e5745fbb2
Update changelog and add test for absolute url rebasing (#15282)
I noticed uncommitted changes of #15275 on my local setup so here's the
updated changelog and the added unit test.
2024-12-03 13:48:52 +01:00
Philipp Spiess
545401469d
Postcss: Run plugin in Once hook (#15273)
Closes #15138

This PR changes the postcss client to run in the `Once` hook instead of
`OnceExit`. This makes sure the postcss order in v4 matches that of v3.

Conceptually this also makes more sense, since we expect tailwindcss to
be run as one of the first plugins in the pipeline (where `OnceExit`
would run it almost at the end).

To make sure it's still possible to use `postcss-import` before and have
it resolve to the right paths, we also needed to change the
`postcss-fix-relative-paths` plugin to run in the `Once` order
(`postcss-import` also uses `Once` order so the order).

## Test Plan

This issue had many ways in which it can manifest. I added a unit test
to ensure the plugin order works but here's a concrete example when
using the postcss plugin in Vite.

### Before

Image `url()`s were not properly handled since the postcss plugin to
transform these was run before Tailwind CSS could generate the class for
it:

<img width="2532" alt="Screenshot 2024-12-02 at 14 55 42"
src="https://github.com/user-attachments/assets/2f23b409-1576-441d-9ffe-6f24ad6e7436">

### After

<img width="2529" alt="Screenshot 2024-12-02 at 14 53 52"
src="https://github.com/user-attachments/assets/b754c3d8-1af1-4aeb-87da-0bfc3ffecdb7">

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-12-03 10:28:51 +01:00
Alem Tuzlak
93b922dcb6
Fix resolution of imported CSS files in Vite SSR builds (#15279)
Fixes #15237

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-12-02 17:08:13 -05:00
Philipp Spiess
89f291c007
Vite: Simplify preprocessor to make it work with Svelte 5 and Vite 6 (#15274)
Closes #15250

This PR simplifies our Vite integration even more. It turns out that in
some projects (see #15250 for the exact repro), the way we invoke
`svelte-preprocess` was actually causing issues in Vite since with Vite,
it's expected to use the `sveltePreprocess` version exported by
`sveltejs/vite-plugin-svelte`.

While trying to change this we noticed that there are different versions
of `sveltejs/vite-plugin-svelte` for Vite 5 and Vite 6 which caused us
to investigate even more and we noticed that we do not even need to
recursively call into the `sveltePreprocess()` as every plugin is run
after each other anyways. This allows us to drop the dependency on
`svelte-preprocess` and simplify the code a bit more, registering only a
`(string) => string` style transformer.

## Test Plan

This was tsted on the repro repo from #15250 as well as the SvelteKit
setup from [my
playgrounds](https://github.com/philipp-spiess/tailwindcss-playgrounds).
Furthermore we tested various combinations of `svelte`,
`@sveltejs/vite-plugin-svelte` and `vite` in our integration test to
ensure everything works as expected.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-12-02 14:48:00 -05:00
Philipp Spiess
667af255b3
Vite: Don't rebase absolute url()s (#15275)
Closes #15269

This PR fixes an issue where our Vite extension was rebasing absolute
urls inside `@import`-ed files. We forgot to cover this when we
implemented the URL rebasing.

## Test Plan

We validated that this fixes the repro in #15269:

<img width="851" alt="Screenshot 2024-12-02 at 18 07 35"
src="https://github.com/user-attachments/assets/3b2c2be3-1f73-469e-9f64-301c6b948b02">

Also added a unit test for this.

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-12-02 14:23:38 -05:00
Robin Malfait
6af483547e
Improve performance of scanning source files (#15270)
This PR improves scanning files by scanning chunks of the files in
parallel. Each chunk is separated by new lines since we can't use
whitespace in classes anyway.

This also means that we can use the power of your CPU to scan files
faster. The extractor itself also has less state to worry about on these
smaller chunks.

On a dedicated benchmark machine: Mac Mini, M1, 16 GB RAM
```shellsession
❯ hyperfine --warmup 15 --runs 50 \
  -n NEW 'bun --bun /Users/ben/github.com/tailwindlabs/tailwindcss/packages/@tailwindcss-cli/src/index.ts -i ./tailwind.css -o out.css' \
  -n CURRENT 'bun --bun /Users/ben/github.com/tailwindlabs/tailwindcss--next/packages/@tailwindcss-cli/src/index.ts -i ./tailwind.css -o out.css'
Benchmark 1: NEW
  Time (mean ± σ):     337.2 ms ±   2.9 ms    [User: 1376.6 ms, System: 80.9 ms]
  Range (min … max):   331.0 ms … 345.3 ms    50 runs

Benchmark 2: CURRENT
  Time (mean ± σ):     730.3 ms ±   3.8 ms    [User: 978.9 ms, System: 78.7 ms]
  Range (min … max):   722.0 ms … 741.8 ms    50 runs

Summary
  NEW ran
    2.17 ± 0.02 times faster than CURRENT
```


On a more powerful machine, MacBook Pro M1 Max, 64 GB RAM, the results
look even more promising:
```shellsession
❯ hyperfine --warmup 15 --runs 50 \
  -n NEW 'bun --bun /Users/robin/github.com/tailwindlabs/tailwindcss/packages/@tailwindcss-cli/src/index.ts -i ./tailwind.css -o out.css' \
  -n CURRENT 'bun --bun /Users/robin/github.com/tailwindlabs/tailwindcss--next/packages/@tailwindcss-cli/src/index.ts -i ./tailwind.css -o out.css'
Benchmark 1: NEW
  Time (mean ± σ):     307.8 ms ±  24.5 ms    [User: 1124.8 ms, System: 187.9 ms]
  Range (min … max):   291.7 ms … 397.9 ms    50 runs

Benchmark 2: CURRENT
  Time (mean ± σ):     754.7 ms ±  27.2 ms    [User: 934.9 ms, System: 217.6 ms]
  Range (min … max):   735.5 ms … 845.6 ms    50 runs

  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

Summary
  NEW ran
    2.45 ± 0.21 times faster than CURRENT
```

> Note: This last benchmark is running on my main machine which is more
"busy" compared to my benchmark machine. Because of this I had to
increase the `--runs` to get statistically better results. There is
still a warning present, but the overall numbers are still very
promising.

---

These benchmarks are running on our Tailwind UI project where we have
>1000 files, and >750 000 lines of code in those files.


| Before | After |
| --- | --- |
| <img width="385" alt="image"
src="https://github.com/user-attachments/assets/4786b842-bedc-4456-a9ca-942f72ca738c">
| <img width="382" alt="image"
src="https://github.com/user-attachments/assets/fb43cff8-95e7-453e-991e-d036c64659ba">
|

---

I am sure there is more we can do here, because reading all of these
1000 files only takes ~10ms, whereas parsing all these files takes
~180ms. But I'm still happy with these results as an incremental
improvement.

For good measure, I also wanted to make sure that we didn't regress on
smaller projects. Running this on Catalyst, we only have to deal with
~100 files and ~18 000 lines of code. In this case reading all the files
takes ~890µs and parsing takes about ~4ms.

| Before | After |
| --- | --- |
| <img width="381" alt="image"
src="https://github.com/user-attachments/assets/25d4859f-d058-4f57-a2f6-219d8c4b1804">
| <img width="390" alt="image"
src="https://github.com/user-attachments/assets/f06d7536-337b-4dc0-a460-6a9f141c65f5">
|

Not a huge difference, still better and definitely no regressions which
sounds like a win to me.

---

**Edit:** after talking to @thecrypticace, instead of splitting on any
whitespace we just split on newlines. This makes the chunks a bit
larger, but it reduces the overhead of the extractor itself. This now
results in a 2.45x speedup in Tailwind UI compared to 1.94x speedup.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-12-02 14:03:10 -05:00
466 changed files with 120056 additions and 30911 deletions

View file

@ -1,12 +1,48 @@
# Contributing
## Requirements
Before getting started, ensure your system has access to the following tools:
- [Node.js](https://nodejs.org/)
- [Rustup](https://rustup.rs/)
- [pnpm](https://pnpm.io/)
## Getting started
```sh
# Install dependencies
pnpm install
# Install Rust toolchain and WASM targets
rustup default stable
rustup target add wasm32-wasip1-threads
# Build the project
pnpm build
```
## Development workflow
During development, you can run tests in watch mode:
```sh
pnpm tdd
```
The `playgrounds` directory contains example projects you can use to test your changes. To start the Vite playground, use:
```sh
pnpm build && pnpm vite
```
## Bug fixes
If you've found a bug in Tailwind that you'd like to fix, [submit a pull request](https://github.com/tailwindlabs/tailwindcss/pulls) with your changes. Include a helpful description of the problem and how your changes address it, and provide tests so we can verify the fix works as expected.
## New features
If there's a new feature you'd like to see added to Tailwind, [share your idea with us](https://github.com/tailwindlabs/tailwindcss/discussions/new?category=ideas) in our discussion forum to get it on our radar as something to consider for a future release.
If there's a new feature you'd like to see added to Tailwind, [share your idea with us](https://github.com/tailwindlabs/tailwindcss/discussions/new?category=ideas) in our discussion forum to get it on our radar as something to consider for a future release before starting work on it.
**Please note that we don't often accept pull requests for new features.** Adding a new feature to Tailwind requires us to think through the entire problem ourselves to make sure we agree with the proposed API, which means the feature needs to be high on our own priority list for us to be able to give it the attention it needs.
@ -14,7 +50,7 @@ If you open a pull request for a new feature, we're likely to close it not becau
## Coding standards
Our code formatting rules are defined in the `"prettier"` section of [package.json](https://github.com/tailwindcss/tailwindcss/blob/next/package.json). You can check your code against these standards by running:
Our code formatting rules are defined in the `"prettier"` section of [package.json](https://github.com/tailwindlabs/tailwindcss/blob/main/package.json). You can check your code against these standards by running:
```sh
pnpm run lint
@ -28,10 +64,40 @@ pnpm run format
## Running tests
You can run the test suite using the following commands:
You can run the TypeScript and Rust test suites using the following command:
```sh
pnpm build && pnpm test
pnpm test
```
Please ensure that the tests are passing when submitting a pull request. If you're adding new features to Tailwind, please include tests.
To run the integration tests, use:
```sh
pnpm build && pnpm test:integrations
```
Additionally, some features require testing in browsers (i.e. to ensure CSS variable resolution works as expected). These can be run via:
```sh
pnpm build && pnpm test:ui
```
Please ensure that all tests are passing when submitting a pull request. If you're adding new features to Tailwind CSS, always include tests.
After a successful build, you can also use the npm package tarballs created inside the `dist/` folder to install your build in other local projects.
## Pull request process
When submitting a pull request:
- Ensure the pull request title and description explain the changes you made and why you made them.
- Include a test plan section that outlines how you tested your contributions. We do not accept contributions without tests.
- Ensure all tests pass. You can add the tag `[ci-all]` in your pull request description to run the test suites across all platforms.
When a pull request is created, Tailwind CSS maintainers will be notified automatically.
## Communication
- **GitHub discussions**: For feature ideas and general questions
- **GitHub issues**: For bug reports
- **GitHub pull requests**: For code contributions

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
custom: ['https://tailwindcss.com/sponsor']

39
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View file

@ -0,0 +1,39 @@
---
name: Bug report
about: If you've already asked for help with a problem and confirmed something is broken with Tailwind CSS itself, create a bug report.
title: ''
labels: ''
assignees: ''
---
<!-- Please provide all of the information requested below. We're a small team and without all of this information it's not possible for us to help and your bug report will be closed. -->
**What version of Tailwind CSS are you using?**
For example: v4.0.6
**What build tool (or framework if it abstracts the build tool) are you using?**
For example: postcss-cli 11.0.0, Next.js 15.1.7, Vite 6.1.0
**What version of Node.js are you using?**
For example: v20.0.0
**What browser are you using?**
For example: Chrome, Safari, or N/A
**What operating system are you using?**
For example: macOS, Windows
**Reproduction URL**
A Tailwind Play link or public GitHub repo that includes a minimal reproduction of the bug. **Please do not link to your actual project**, what we need instead is a _minimal_ reproduction in a fresh project without any unnecessary code. This means it doesn't matter if your real project is private/confidential, since we want a link to a separate, isolated reproduction anyways.
A reproduction is **required** when filing an issue — any issue opened without a reproduction will be closed and you'll be asked to create a new issue that includes a reproduction. We're a small team and we can't keep up with the volume of issues we receive if we need to reproduce each issue from scratch ourselves.
**Describe your issue**
Describe the problem you're seeing, any important steps to reproduce and what behavior you expect instead.

View file

@ -6,9 +6,6 @@ contact_links:
- name: Feature Request
url: https://github.com/tailwindlabs/tailwindcss/discussions/new?category=ideas
about: 'Suggest any ideas you have using our discussion forums.'
- name: Bug Report
url: https://github.com/tailwindlabs/tailwindcss/issues/new?body=%3C%21--%20Please%20provide%20all%20of%20the%20information%20requested%20below.%20We%27re%20a%20small%20team%20and%20without%20all%20of%20this%20information%20it%27s%20not%20possible%20for%20us%20to%20help%20and%20your%20bug%20report%20will%20be%20closed.%20--%3E%0A%0A%2A%2AWhat%20version%20of%20Tailwind%20CSS%20are%20you%20using%3F%2A%2A%0A%0AFor%20example%3A%20v2.0.4%0A%0A%2A%2AWhat%20build%20tool%20%28or%20framework%20if%20it%20abstracts%20the%20build%20tool%29%20are%20you%20using%3F%2A%2A%0A%0AFor%20example%3A%20postcss-cli%208.3.1%2C%20Next.js%2010.0.9%2C%20webpack%205.28.0%0A%0A%2A%2AWhat%20version%20of%20Node.js%20are%20you%20using%3F%2A%2A%0A%0AFor%20example%3A%20v12.0.0%0A%0A%2A%2AWhat%20browser%20are%20you%20using%3F%2A%2A%0A%0AFor%20example%3A%20Chrome%2C%20Safari%2C%20or%20N%2FA%0A%0A%2A%2AWhat%20operating%20system%20are%20you%20using%3F%2A%2A%0A%0AFor%20example%3A%20macOS%2C%20Windows%0A%0A%2A%2AReproduction%20URL%2A%2A%0A%0AA%20Tailwind%20Play%20link%20or%20public%20GitHub%20repo%20that%20includes%20a%20minimal%20reproduction%20of%20the%20bug.%20%2A%2APlease%20do%20not%20link%20to%20your%20actual%20project%2A%2A%2C%20what%20we%20need%20instead%20is%20a%20_minimal_%20reproduction%20in%20a%20fresh%20project%20without%20any%20unnecessary%20code.%20This%20means%20it%20doesn%27t%20matter%20if%20your%20real%20project%20is%20private%2Fconfidential%2C%20since%20we%20want%20a%20link%20to%20a%20separate%2C%20isolated%20reproduction%20anyways.%0A%0AA%20reproduction%20is%20%2A%2Arequired%2A%2A%20when%20filing%20an%20issue%20%E2%80%94%20any%20issue%20opened%20without%20a%20reproduction%20will%20be%20closed%20and%20you%27ll%20be%20asked%20to%20create%20a%20new%20issue%20that%20includes%20a%20reproduction.%20We%27re%20a%20small%20team%20and%20we%20can%27t%20keep%20up%20with%20the%20volume%20of%20issues%20we%20receive%20if%20we%20need%20to%20reproduce%20each%20issue%20from%20scratch%20ourselves.%0A%0A%2A%2ADescribe%20your%20issue%2A%2A%0A%0ADescribe%20the%20problem%20you%27re%20seeing%2C%20any%20important%20steps%20to%20reproduce%20and%20what%20behavior%20you%20expect%20instead.
about: If you've already asked for help with a problem and confirmed something is broken with Tailwind CSS itself, create a bug report.
- name: Documentation Issue
url: https://github.com/tailwindlabs/tailwindcss.com
about: 'For documentation issues, suggest changes on our documentation repository.'

View file

@ -4,8 +4,26 @@
**Please ask first before starting work on any significant new features.**
It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create an issue to first discuss any significant new features. This includes things like adding new utilities, creating new at-rules, or adding new component examples to the documentation.
It's never a fun experience to have your pull request declined after investing a lot of time and effort into a new feature. To avoid this from happening, we request that contributors create a discussion to first discuss any significant new features.
https://github.com/tailwindcss/tailwindcss/blob/master/.github/CONTRIBUTING.md
For more info, check out the contributing guide:
https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md
-->
## Summary
<!--
Provide a summary of the issue and the changes you're making. How does your change solve the problem?
-->
## Test plan
<!--
Explain how you tested your changes. Include the exact commands that you used to verify the change works and include screenshots/screen recordings of the update behavior in the browser if applicable.
-->

View file

@ -2,47 +2,68 @@ name: CI
on:
push:
branches: [next]
branches: [main]
pull_request:
permissions:
contents: read
env:
NODE_VERSION: 24
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
strategy:
fail-fast: false
matrix:
node-version: [20]
runner: [namespace-profile-default, windows-latest, macos-14]
# Exclude windows and macos from being built on feature branches
on-next-branch:
- ${{ github.ref == 'refs/heads/next' }}
exclude:
- on-next-branch: false
runner: windows-latest
- on-next-branch: false
runner: macos-14
runner:
- name: Windows
os: windows-latest
runs-on: ${{ matrix.runner }}
- name: Linux
os: namespace-profile-default
# Playwright 1.62+ dropped WebKit support for macOS 14, and hangs
# instead of failing when launching WebKit on a macos-14 runner.
- name: macOS
os: macos-15
# Exclude windows and macos from being built on feature branches
run-all:
- ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.body, '[ci-all]') || github.event.pull_request.user.login == 'depfu[bot]' }}
exclude:
- run-all: false
runner:
name: Windows
- run-all: false
runner:
name: macOS
runs-on: ${{ matrix.runner.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: ${{ matrix.runner.name }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
persist-credentials: false
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: ${{ env.NODE_VERSION }}
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
uses: actions/cache@v4
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
@ -51,17 +72,25 @@ jobs:
# Cache the `oxide` Rust build
- name: Cache oxide build
uses: actions/cache@v4
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
./target/
./crates/node/*.node
./crates/node/index.js
./crates/node/*.wasm
./crates/node/index.d.ts
./crates/node/index.js
./crates/node/browser.js
./crates/node/tailwindcss-oxide.wasi-browser.js
./crates/node/tailwindcss-oxide.wasi.cjs
./crates/node/wasi-worker-browser.mjs
./crates/node/wasi-worker.mjs
key: ${{ runner.os }}-oxide-${{ hashFiles('./crates/**/*') }}
- name: Setup WASM target
run: rustup target add wasm32-wasip1-threads
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
@ -72,25 +101,24 @@ jobs:
- name: Lint
run: pnpm run lint
# Only lint on linux to avoid \r\n line ending errors
if: matrix.runner == 'ubuntu-latest'
if: matrix.runner.os == 'ubuntu-latest'
- name: Test
run: pnpm run test
- name: Integration Tests
run: pnpm run test:integrations
env:
GITHUB_WORKSPACE: ${{ github.workspace }}
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npm run test:ui
notify:
if: ${{ always() && github.ref == 'refs/heads/main' && needs.tests.result == 'failure' }}
needs: tests
runs-on: ubuntu-latest
steps:
- name: Notify Discord
if: failure() && github.ref == 'refs/heads/next'
uses: discord-actions/message@v2
uses: discord-actions/message@5c7149c81a83146e5d01f142be1bf87a61831c4d # v2
with:
webhookUrl: ${{ secrets.DISCORD_WEBHOOK_URL }}
message: 'The [most recent build](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) on the `next` branch has failed.'
message: 'The [most recent ${{ github.workflow }} workflow](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) on the `main` branch has failed.'

125
.github/workflows/integration-tests.yml vendored Normal file
View file

@ -0,0 +1,125 @@
name: Integration Tests
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
env:
NODE_VERSION: 24
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
strategy:
fail-fast: false
matrix:
runner:
- name: Windows
os: windows-latest
- name: Linux
os: namespace-profile-default
- name: macOS
os: macos-14
integration:
- upgrade
- vite
- cli
- postcss
- oxide
- webpack
# Exclude windows and macos from being built on feature branches
run-all:
- ${{ github.ref == 'refs/heads/main' || contains(github.event.pull_request.body, '[ci-all]') }}
exclude:
- run-all: false
runner:
name: Windows
- run-all: false
runner:
name: macOS
runs-on: ${{ matrix.runner.os }}
timeout-minutes: 30
name: ${{ matrix.runner.name }} / ${{ matrix.integration }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: ${{ env.NODE_VERSION }}
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# Cache the `oxide` Rust build
- name: Cache oxide build
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
./crates/node/*.node
./crates/node/*.wasm
./crates/node/index.d.ts
./crates/node/index.js
./crates/node/browser.js
./crates/node/tailwindcss-oxide.wasi-browser.js
./crates/node/tailwindcss-oxide.wasi.cjs
./crates/node/wasi-worker-browser.mjs
./crates/node/wasi-worker.mjs
key: ${{ runner.os }}-oxide-${{ hashFiles('./crates/**/*') }}
- name: Setup WASM target
run: rustup target add wasm32-wasip1-threads
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm run build
env:
CARGO_PROFILE_RELEASE_LTO: 'off'
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER: 'lld-link'
- name: Test ${{ matrix.integration }}
run: pnpm run test:integrations ./integrations/${{ matrix.integration }}
env:
GITHUB_WORKSPACE: ${{ github.workspace }}
notify:
if: ${{ always() && github.ref == 'refs/heads/main' && needs.tests.result == 'failure' }}
needs: tests
runs-on: ubuntu-latest
steps:
- name: Notify Discord
uses: discord-actions/message@5c7149c81a83146e5d01f142be1bf87a61831c4d # v2
with:
webhookUrl: ${{ secrets.DISCORD_WEBHOOK_URL }}
message: 'The [most recent ${{ github.workflow }} workflow](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) on the `main` branch has failed.'

View file

@ -2,21 +2,306 @@ name: Prepare Release
on:
workflow_dispatch:
inputs:
dry_run:
description: Skip creating the draft GitHub release
required: false
default: true
type: boolean
push:
tags:
- 'v*'
env:
CI: true
APP_NAME: tailwindcss-oxide
NODE_VERSION: 24
PNPM_VERSION: '11.9.0'
OXIDE_LOCATION: ./crates/node
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: macos-12
timeout-minutes: 15
strategy:
matrix:
node-version: [16]
include:
# Windows
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: windows-latest
target: aarch64-pc-windows-msvc
# macOS
- os: macos-latest
target: x86_64-apple-darwin
strip: strip -x # Must use -x on macOS. This produces larger results on linux.
- os: macos-latest
target: aarch64-apple-darwin
page-size: 14
strip: strip -x # Must use -x on macOS. This produces larger results on linux.
# Android
- os: ubuntu-latest
target: aarch64-linux-android
strip: ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip
- os: ubuntu-latest
target: armv7-linux-androideabi
strip: ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip
# Linux
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
strip: strip
build-flags: --use-napi-cross
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
strip: aarch64-linux-gnu-strip
build-flags: --use-napi-cross
- os: ubuntu-latest
target: armv7-unknown-linux-gnueabihf
strip: arm-linux-gnueabihf-strip
build-flags: --use-napi-cross
- os: ubuntu-latest
target: aarch64-unknown-linux-musl
strip-zig: true
build-flags: -x
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
strip: strip
build-flags: -x
name: Build ${{ matrix.target }} (oxide)
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: ${{ env.PNPM_VERSION }}
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: ${{ env.NODE_VERSION }}
package-manager-cache: false
- name: Install gcc-arm-linux-gnueabihf
if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }}
run: |
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
- name: Install binutils-aarch64-linux-gnu
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
run: |
sudo apt-get update
sudo apt-get install binutils-aarch64-linux-gnu -y
- uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
if: ${{ contains(matrix.target, 'musl') }}
with:
version: 0.14.1
use-cache: false
- name: Install cargo-zigbuild
uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2
if: ${{ contains(matrix.target, 'musl') }}
env:
GITHUB_TOKEN: ${{ github.token }}
with:
tool: cargo-zigbuild
- name: Setup rust target
run: rustup target add ${{ matrix.target }}
- name: Install dependencies
run: pnpm install --ignore-scripts --frozen-lockfile --filter=!./playgrounds/*
- name: Build release
run: pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform --target=${{ matrix.target }} ${{ matrix.build-flags }}
env:
RUST_TARGET: ${{ matrix.target }}
JEMALLOC_SYS_WITH_LG_PAGE: ${{ matrix.page-size }}
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
if: ${{ matrix.strip || matrix.strip-zig }}
env:
STRIP_COMMAND: ${{ matrix.strip }}
STRIP_ZIG: ${{ matrix.strip-zig }}
run: |
if [ "$STRIP_ZIG" = "true" ]; then
for file in ${{ env.OXIDE_LOCATION }}/*.node; do
zig objcopy --strip-all "$file" "$file.stripped"
mv "$file.stripped" "$file"
done
exit 0
fi
eval "$STRIP_COMMAND ${{ env.OXIDE_LOCATION }}/*.node"
- name: Upload artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: bindings-${{ matrix.target }}
path: ${{ env.OXIDE_LOCATION }}/*.node
build-freebsd:
name: Build x86_64-unknown-freebsd (OXIDE)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Build FreeBSD
uses: cross-platform-actions/action@cdc9ee69ef84a5f2e59c9058335d9c57bcb4ac86 # v0.25.0
env:
DEBUG: napi:*
RUSTUP_HOME: /usr/local/rustup
CARGO_HOME: /usr/local/cargo
RUSTUP_IO_THREADS: 1
RUST_TARGET: x86_64-unknown-freebsd
with:
operating_system: freebsd
version: '14.0'
memory: 13G
cpu_count: 3
environment_variables: 'DEBUG RUSTUP_IO_THREADS'
shell: bash
run: |
sudo pkg install -y -f curl node libnghttp2 npm
sudo npm install -g pnpm@${{ env.PNPM_VERSION }} --unsafe-perm=true
curl -sSf https://static.rust-lang.org/rustup/archive/1.27.1/x86_64-unknown-freebsd/rustup-init --output rustup-init
chmod +x rustup-init
./rustup-init -y --profile minimal
source "$HOME/.cargo/env"
pnpm install --ignore-scripts --frozen-lockfile --filter=!./playgrounds/* || true
echo "~~~~ rustc --version ~~~~"
rustc --version
echo "~~~~ node -v ~~~~"
node -v
echo "~~~~ pnpm --version ~~~~"
pnpm --version
pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform
strip -x ${{ env.OXIDE_LOCATION }}/*.node
ls -la ${{ env.OXIDE_LOCATION }}
- name: Upload artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: bindings-x86_64-unknown-freebsd
path: ${{ env.OXIDE_LOCATION }}/*.node
prepare:
runs-on: macos-14
timeout-minutes: 15
name: Build and release Tailwind CSS
permissions:
contents: write # Required for creating releases
needs:
- build
- build-freebsd
steps:
- run: echo "stub"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 20
persist-credentials: false
- run: git fetch --tags -f
- name: Resolve version
id: vars
run: |
echo "TAG_NAME=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: ${{ env.PNPM_VERSION }}
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
package-manager-cache: false
- name: Setup WASM target
run: rustup target add wasm32-wasip1-threads
- name: Install dependencies
run: pnpm --filter=!./playgrounds/* install --frozen-lockfile
- name: Download artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
path: ${{ env.OXIDE_LOCATION }}
- name: Move artifacts
run: |
cd ${{ env.OXIDE_LOCATION }}
cp bindings-x86_64-pc-windows-msvc/* ./npm/win32-x64-msvc/
cp bindings-aarch64-pc-windows-msvc/* ./npm/win32-arm64-msvc/
cp bindings-x86_64-apple-darwin/* ./npm/darwin-x64/
cp bindings-aarch64-apple-darwin/* ./npm/darwin-arm64/
cp bindings-aarch64-linux-android/* ./npm/android-arm64/
cp bindings-armv7-linux-androideabi/* ./npm/android-arm-eabi/
cp bindings-aarch64-unknown-linux-gnu/* ./npm/linux-arm64-gnu/
cp bindings-aarch64-unknown-linux-musl/* ./npm/linux-arm64-musl/
cp bindings-armv7-unknown-linux-gnueabihf/* ./npm/linux-arm-gnueabihf/
cp bindings-x86_64-unknown-linux-gnu/* ./npm/linux-x64-gnu/
cp bindings-x86_64-unknown-linux-musl/* ./npm/linux-x64-musl/
cp bindings-x86_64-unknown-freebsd/* ./npm/freebsd-x64/
- name: Build Tailwind CSS
run: pnpm run build
env:
FEATURES_ENV: stable
- name: Run pre-publish optimizations scripts
run: node ./scripts/pre-publish-optimizations.mjs
- name: Lock pre-release versions
run: node ./scripts/lock-pre-release-versions.mjs
- name: Get release notes
run: |
RELEASE_NOTES=$(node ./scripts/release-notes.mjs)
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
echo "$RELEASE_NOTES" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Upload standalone artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: tailwindcss-standalone
path: packages/@tailwindcss-standalone/dist/
- name: Upload npm package tarballs
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: npm-package-tarballs
path: dist/*.tgz
- name: Prepare GitHub Release
if: ${{ !inputs.dry_run }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${TAG_NAME}" \
--draft \
--title "${TAG_NAME}" \
--notes "${RELEASE_NOTES}" \
packages/@tailwindcss-standalone/dist/sha256sums.txt \
packages/@tailwindcss-standalone/dist/tailwindcss-linux-arm64 \
packages/@tailwindcss-standalone/dist/tailwindcss-linux-arm64-musl \
packages/@tailwindcss-standalone/dist/tailwindcss-linux-x64 \
packages/@tailwindcss-standalone/dist/tailwindcss-linux-x64-musl \
packages/@tailwindcss-standalone/dist/tailwindcss-macos-arm64 \
packages/@tailwindcss-standalone/dist/tailwindcss-macos-x64 \
packages/@tailwindcss-standalone/dist/tailwindcss-windows-x64.exe

View file

@ -1,22 +1,34 @@
name: Release
on:
push:
branches: [main]
release:
types: [published]
workflow_dispatch:
inputs:
release_channel:
description: 'Release channel'
required: false
default: 'next'
type: string
channel:
description: Release channel to publish
required: true
default: insiders
type: choice
options:
- insiders
- release
permissions:
contents: read
env:
APP_NAME: tailwindcss-oxide
NODE_VERSION: 20
NODE_VERSION: 24
PNPM_VERSION: '11.9.0'
OXIDE_LOCATION: ./crates/node
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
strategy:
@ -46,163 +58,207 @@ jobs:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
strip: strip
container:
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
build-flags: --use-napi-cross
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
strip: llvm-strip
container:
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
strip: aarch64-linux-gnu-strip
build-flags: --use-napi-cross
- os: ubuntu-latest
target: armv7-unknown-linux-gnueabihf
strip: llvm-strip
container:
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-zig
strip: arm-linux-gnueabihf-strip
build-flags: --use-napi-cross
- os: ubuntu-latest
target: aarch64-unknown-linux-musl
strip: aarch64-linux-musl-strip
download: true
container:
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
strip-zig: true
build-flags: -x
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
strip: strip
download: true
container:
image: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
build-flags: -x
name: Build ${{ matrix.target }} (OXIDE)
name: Build ${{ matrix.target }} (oxide)
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: ${{ env.PNPM_VERSION }}
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
package-manager-cache: false
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# Cache the `oxide` Rust build
- name: Cache oxide build
uses: actions/cache@v4
with:
path: |
./oxide/target/
./crates/node/*.node
./crates/node/index.js
./crates/node/index.d.ts
key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./crates/**/*') }}
- name: Install Node.JS
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install Rust (Stable)
if: ${{ matrix.download }}
- name: Install gcc-arm-linux-gnueabihf
if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }}
run: |
rustup default stable
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
- name: Install binutils-aarch64-linux-gnu
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
run: |
sudo apt-get update
sudo apt-get install binutils-aarch64-linux-gnu -y
- uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2
if: ${{ contains(matrix.target, 'musl') }}
with:
version: 0.14.1
use-cache: false
- name: Install cargo-zigbuild
uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2
if: ${{ contains(matrix.target, 'musl') }}
env:
GITHUB_TOKEN: ${{ github.token }}
with:
tool: cargo-zigbuild
- name: Setup rust target
run: rustup target add ${{ matrix.target }}
- name: Install dependencies
run: pnpm install --ignore-scripts --filter=!./playgrounds/*
run: pnpm install --ignore-scripts --frozen-lockfile --filter=!./playgrounds/*
- name: Build release
run: pnpm run --filter ${{ env.OXIDE_LOCATION }} build
run: pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform --target=${{ matrix.target }} ${{ matrix.build-flags }}
env:
RUST_TARGET: ${{ matrix.target }}
JEMALLOC_SYS_WITH_LG_PAGE: ${{ matrix.page-size }}
- name: Strip debug symbols # https://github.com/rust-lang/rust/issues/46034
if: ${{ matrix.strip }}
run: ${{ matrix.strip }} ${{ env.OXIDE_LOCATION }}/*.node
if: ${{ matrix.strip || matrix.strip-zig }}
env:
STRIP_COMMAND: ${{ matrix.strip }}
STRIP_ZIG: ${{ matrix.strip-zig }}
run: |
if [ "$STRIP_ZIG" = "true" ]; then
for file in ${{ env.OXIDE_LOCATION }}/*.node; do
zig objcopy --strip-all "$file" "$file.stripped"
mv "$file.stripped" "$file"
done
exit 0
fi
eval "$STRIP_COMMAND ${{ env.OXIDE_LOCATION }}/*.node"
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: bindings-${{ matrix.target }}
path: ${{ env.OXIDE_LOCATION }}/*.node
build-freebsd:
name: Build x86_64-unknown-freebsd (OXIDE)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Build FreeBSD
uses: cross-platform-actions/action@cdc9ee69ef84a5f2e59c9058335d9c57bcb4ac86 # v0.25.0
env:
DEBUG: napi:*
RUSTUP_HOME: /usr/local/rustup
CARGO_HOME: /usr/local/cargo
RUSTUP_IO_THREADS: 1
RUST_TARGET: x86_64-unknown-freebsd
with:
operating_system: freebsd
version: '14.0'
memory: 13G
cpu_count: 3
environment_variables: 'DEBUG RUSTUP_IO_THREADS'
shell: bash
run: |
sudo pkg install -y -f curl node libnghttp2 npm
sudo npm install -g pnpm@${{ env.PNPM_VERSION }} --unsafe-perm=true
curl -sSf https://static.rust-lang.org/rustup/archive/1.27.1/x86_64-unknown-freebsd/rustup-init --output rustup-init
chmod +x rustup-init
./rustup-init -y --profile minimal
source "$HOME/.cargo/env"
echo "~~~~ rustc --version ~~~~"
rustc --version
echo "~~~~ node -v ~~~~"
node -v
echo "~~~~ pnpm --version ~~~~"
pnpm --version
pnpm install --ignore-scripts --frozen-lockfile --filter=!./playgrounds/* || true
pnpm run --filter ${{ env.OXIDE_LOCATION }} build:platform
strip -x ${{ env.OXIDE_LOCATION }}/*.node
ls -la ${{ env.OXIDE_LOCATION }}
- name: Upload artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: bindings-x86_64-unknown-freebsd
path: ${{ env.OXIDE_LOCATION }}/*.node
release:
runs-on: macos-14
timeout-minutes: 15
name: Build and release Tailwind CSS
name: Build and publish Tailwind CSS
permissions:
contents: write # for softprops/action-gh-release to create GitHub release
contents: read
# https://docs.npmjs.com/generating-provenance-statements#publishing-packages-with-provenance-via-github-actions
id-token: write
needs:
- build
- build-freebsd
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-tags: true
fetch-depth: 20
persist-credentials: false
- run: git fetch --tags -f
- name: Resolve version
id: vars
run: |
echo "TAG_NAME=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: ${{ env.PNPM_VERSION }}
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
package-manager-cache: false
# Cargo already skips downloading dependencies if they already exist
- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# npm trusted publishing validates the caller workflow filename, so all npm publishes live here.
# This workflow rebuilds the publish artifacts instead of depending on prepare-release.yml.
- name: Resolve release metadata
env:
INPUT_CHANNEL: ${{ github.event.inputs.channel || '' }}
run: |
if [[ "${{ github.event_name }}" == "release" || "$INPUT_CHANNEL" == "release" ]]; then
release_channel=$(node ./scripts/release-channel.js)
# Cache the `oxide` Rust build
- name: Cache oxide build
uses: actions/cache@v4
with:
path: |
./oxide/target/
./crates/node/*.node
./crates/node/index.js
./crates/node/index.d.ts
key: ${{ runner.os }}-${{ matrix.target }}-oxide-${{ hashFiles('./crates/**/*') }}
echo "RELEASE_KIND=release" >> $GITHUB_ENV
echo "RELEASE_CHANNEL=$release_channel" >> $GITHUB_ENV
echo "FEATURES_ENV=stable" >> $GITHUB_ENV
else
sha_short=$(git rev-parse --short HEAD)
echo "RELEASE_KIND=insiders" >> $GITHUB_ENV
echo "RELEASE_CHANNEL=insiders" >> $GITHUB_ENV
echo "SHA_SHORT=$sha_short" >> $GITHUB_ENV
echo "INSIDERS_VERSION=0.0.0-insiders.$sha_short" >> $GITHUB_ENV
fi
- name: Setup WASM target
run: rustup target add wasm32-wasip1-threads
- name: Install dependencies
run: pnpm --filter=!./playgrounds/* install
run: pnpm --filter=!./playgrounds/* install --frozen-lockfile
- name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
path: ${{ env.OXIDE_LOCATION }}
@ -220,45 +276,83 @@ jobs:
cp bindings-armv7-unknown-linux-gnueabihf/* ./npm/linux-arm-gnueabihf/
cp bindings-x86_64-unknown-linux-gnu/* ./npm/linux-x64-gnu/
cp bindings-x86_64-unknown-linux-musl/* ./npm/linux-x64-musl/
cp bindings-x86_64-unknown-freebsd/* ./npm/freebsd-x64/
- name: 'Version based on commit: ${{ env.INSIDERS_VERSION }}'
if: env.RELEASE_KIND == 'insiders'
run: pnpm run version-packages ${INSIDERS_VERSION}
- name: Build Tailwind CSS
if: env.RELEASE_KIND == 'insiders'
run: pnpm run build
- name: Build Tailwind CSS
if: env.RELEASE_KIND == 'release'
run: pnpm run build
env:
FEATURES_ENV: ${{ env.FEATURES_ENV }}
- name: Run pre-publish optimizations scripts
run: node ./scripts/pre-publish-optimizations.mjs
- name: Lock pre-release versions
run: node ./scripts/lock-pre-release-versions.mjs
- name: Get release notes
run: |
RELEASE_NOTES=$(node ./scripts/release-notes.mjs)
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
echo "$RELEASE_NOTES" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Upload Standalone Artifacts
uses: actions/upload-artifact@v4
- name: Upload npm package tarballs
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: tailwindcss-standalone
path: packages/@tailwindcss-standalone/dist/
name: npm-package-tarballs
path: dist/*.tgz
- name: Publish
run: pnpm --recursive publish --tag ${{ inputs.release_channel }} --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
pnpm --recursive --filter="!@tailwindcss/oxide-wasm32-wasi" publish --tag ${RELEASE_CHANNEL} --no-git-checks
# The wasm package needs a special npm config that isn't read when pnpm --recursive is used
pushd crates/node/npm/wasm32-wasi; pnpm publish --tag ${RELEASE_CHANNEL} --no-git-checks --config.node-linker=hoisted; popd;
- name: Release
uses: softprops/action-gh-release@v2
- name: Trigger Tailwind Play update
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
draft: true
tag_name: ${{ env.TAG_NAME }}
body: |
${{ env.RELEASE_NOTES }}
files: |
packages/@tailwindcss-standalone/dist/sha256sums.txt
packages/@tailwindcss-standalone/dist/tailwindcss-linux-arm64
packages/@tailwindcss-standalone/dist/tailwindcss-linux-x64
packages/@tailwindcss-standalone/dist/tailwindcss-macos-arm64
packages/@tailwindcss-standalone/dist/tailwindcss-macos-x64
packages/@tailwindcss-standalone/dist/tailwindcss-windows-x64.exe
github-token: ${{ secrets.TAILWIND_PLAY_TOKEN }}
script: |
await github.rest.actions.createWorkflowDispatch({
owner: 'tailwindlabs',
repo: 'upgrades',
ref: 'main',
workflow_id: 'upgrade-tailwindcss.yml'
})
notify:
if: ${{ always() && (needs.build.result == 'failure' || needs.build-freebsd.result == 'failure' || needs.release.result == 'failure') }}
needs:
- build
- build-freebsd
- release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- name: Resolve release label
id: release
env:
INPUT_CHANNEL: ${{ github.event.inputs.channel || '' }}
RELEASE_TAG: ${{ github.event.release.tag_name || '' }}
run: |
if [[ "${{ github.event_name }}" == "release" ]]; then
tag_name="${RELEASE_TAG:-${GITHUB_REF_NAME}}"
echo "label=release ${tag_name}" >> $GITHUB_OUTPUT
elif [[ "$INPUT_CHANNEL" == "release" ]]; then
version=$(node -p "require('./packages/tailwindcss/package.json').version")
echo "label=release v${version}" >> $GITHUB_OUTPUT
else
sha_short=$(git rev-parse --short HEAD)
echo "label=insiders release 0.0.0-insiders.${sha_short}" >> $GITHUB_OUTPUT
fi
- name: Notify Discord
uses: discord-actions/message@5c7149c81a83146e5d01f142be1bf87a61831c4d # v2
with:
webhookUrl: ${{ secrets.DISCORD_WEBHOOK_URL }}
message: 'The [most recent ${{ github.workflow }} workflow](<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>) for `${{ steps.release.outputs.label }}` has failed.'

2
.gitignore vendored
View file

@ -7,4 +7,4 @@ playwright-report/
blob-report/
playwright/.cache/
target/
.debug
.debug/

1
.npmrc
View file

@ -1 +0,0 @@
auto-install-peers = true

View file

@ -4,5 +4,6 @@ pnpm-lock.yaml
target/
crates/node/index.d.ts
crates/node/index.js
crates/ignore/
.next
.fingerprint

File diff suppressed because it is too large Load diff

364
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "aho-corasick"
@ -11,6 +11,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "bexpand"
version = "1.2.0"
@ -29,12 +35,12 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bstr"
version = "1.10.0"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
dependencies = [
"memchr",
"regex-automata 0.4.8",
"regex-automata 0.4.18",
"serde",
]
@ -45,32 +51,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "convert_case"
version = "0.6.0"
name = "classification-macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "console"
version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c"
dependencies = [
"encode_unicode",
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "convert_case"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "crossbeam"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.13"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
@ -94,15 +107,6 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
@ -111,13 +115,15 @@ checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "ctor"
version = "0.2.1"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990a40740adf249724a6000c0fc4bd574712f50bb17c2d6f6cec837ae2f0ee75"
dependencies = [
"quote",
"syn",
]
checksum = "2d83cb7e7a873830708d6b02a78cd36a592c6fa14bf267b68725103b85c0d77f"
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "dunce"
@ -131,6 +137,12 @@ version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "errno"
version = "0.3.9"
@ -141,6 +153,15 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "fast-glob"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eca69ef247d19faa15ac0156968637440824e5ff22baa5ee0cd35b2f7ea6a0f"
dependencies = [
"arrayvec",
]
[[package]]
name = "fastrand"
version = "2.1.1"
@ -148,21 +169,103 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "glob-match"
version = "0.2.1"
name = "futures"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d"
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
name = "futures-macro"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
name = "futures-task"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"slab",
]
[[package]]
name = "globset"
version = "0.4.15"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19"
checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata 0.4.8",
"regex-automata 0.4.18",
"regex-syntax 0.8.5",
]
@ -173,7 +276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
dependencies = [
"bitflags",
"ignore",
"ignore 0.4.23",
"walkdir",
]
@ -187,12 +290,41 @@ dependencies = [
"globset",
"log",
"memchr",
"regex-automata 0.4.8",
"regex-automata 0.4.18",
"same-file",
"walkdir",
"winapi-util",
]
[[package]]
name = "ignore"
version = "0.4.33"
dependencies = [
"bstr",
"crossbeam-channel",
"crossbeam-deque",
"dunce",
"globset",
"log",
"memchr",
"regex-automata 0.4.18",
"same-file",
"walkdir",
"winapi-util",
]
[[package]]
name = "insta"
version = "1.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82"
dependencies = [
"console",
"once_cell",
"similar",
"tempfile",
]
[[package]]
name = "itertools"
version = "0.11.0"
@ -216,12 +348,12 @@ checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
[[package]]
name = "libloading"
version = "0.8.5"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
dependencies = [
"cfg-if",
"windows-targets",
"windows-link",
]
[[package]]
@ -259,31 +391,33 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "napi"
version = "2.16.11"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53575dfa17f208dd1ce3a2da2da4659aae393b256a472f2738a8586a6c4107fd"
checksum = "de33522036981030a75c231829566bc63414e08101a6f5ff4ac6cef19c8e0941"
dependencies = [
"bitflags",
"ctor",
"napi-derive",
"futures",
"napi-build",
"napi-sys",
"once_cell",
"nohash-hasher",
"rustc-hash",
]
[[package]]
name = "napi-build"
version = "2.0.1"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e"
checksum = "c9c366d2c8c60b86fa632df75f745509b52f9128f91a6bad4c796e44abb505e1"
[[package]]
name = "napi-derive"
version = "2.16.12"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17435f7a00bfdab20b0c27d9c56f58f6499e418252253081bfff448099da31d1"
checksum = "a49c513341a61a16a10af6efcce46b30d0822ba2d4fb197d24d33dfc199c78d5"
dependencies = [
"cfg-if",
"convert_case",
"ctor",
"napi-derive-backend",
"proc-macro2",
"quote",
@ -292,28 +426,32 @@ dependencies = [
[[package]]
name = "napi-derive-backend"
version = "1.0.74"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "967c485e00f0bf3b1bdbe510a38a4606919cf1d34d9a37ad41f25a81aa077abe"
checksum = "d60b5d773ad46c698c8cc2cd9fde0b283d39cbb7f71c04bee633c7bdba4423bd"
dependencies = [
"convert_case",
"once_cell",
"proc-macro2",
"quote",
"regex",
"semver",
"syn",
]
[[package]]
name = "napi-sys"
version = "2.4.0"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
checksum = "85fbf1fa9f1babfe396d74bbbf52b3643770243e8f5b0b46715d4caf7f0dfc9a"
dependencies = [
"libloading",
]
[[package]]
name = "nohash-hasher"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "nom"
version = "7.1.3"
@ -336,9 +474,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.19.0"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "overload"
@ -352,6 +490,16 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]]
name = "proc-macro2"
version = "1.0.86"
@ -363,18 +511,18 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.28"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.10.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [
"either",
"rayon-core",
@ -382,9 +530,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.12.1"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
@ -392,13 +540,14 @@ dependencies = [
[[package]]
name = "regex"
version = "1.8.3"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.7.2",
"regex-automata 0.4.18",
"regex-syntax 0.8.5",
]
[[package]]
@ -412,9 +561,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.8"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
dependencies = [
"aho-corasick",
"memchr",
@ -427,12 +576,6 @@ version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
[[package]]
name = "regex-syntax"
version = "0.8.5"
@ -441,9 +584,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustc-hash"
version = "2.0.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
@ -488,6 +631,18 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "similar"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.10.0"
@ -496,9 +651,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "syn"
version = "2.0.18"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
@ -522,17 +677,21 @@ version = "0.1.0"
dependencies = [
"bexpand",
"bstr",
"crossbeam",
"classification-macros",
"dunce",
"glob-match",
"fast-glob",
"globwalk",
"ignore",
"ignore 0.4.33",
"insta",
"log",
"pretty_assertions",
"rayon",
"regex",
"rustc-hash",
"tempfile",
"tracing",
"tracing-subscriber",
"unicode-width",
"walkdir",
]
@ -632,6 +791,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "valuable"
version = "0.1.0"
@ -679,6 +844,12 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.52.0"
@ -697,6 +868,15 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
@ -760,3 +940,9 @@ name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"

View file

@ -13,10 +13,10 @@
</p>
<p align="center">
<a href="https://github.com/tailwindlabs/tailwindcss/actions"><img src="https://img.shields.io/github/actions/workflow/status/tailwindlabs/tailwindcss/ci.yml?branch=next" alt="Build Status"></a>
<a href="https://github.com/tailwindlabs/tailwindcss/actions"><img src="https://img.shields.io/github/actions/workflow/status/tailwindlabs/tailwindcss/ci.yml?branch=main" alt="Build Status"></a>
<a href="https://www.npmjs.com/package/tailwindcss"><img src="https://img.shields.io/npm/dt/tailwindcss.svg" alt="Total Downloads"></a>
<a href="https://github.com/tailwindcss/tailwindcss/releases"><img src="https://img.shields.io/npm/v/tailwindcss.svg" alt="Latest Release"></a>
<a href="https://github.com/tailwindcss/tailwindcss/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/tailwindcss.svg" alt="License"></a>
<a href="https://github.com/tailwindlabs/tailwindcss/releases"><img src="https://img.shields.io/npm/v/tailwindcss.svg" alt="Latest Release"></a>
<a href="https://github.com/tailwindlabs/tailwindcss/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/tailwindcss.svg" alt="License"></a>
</p>
---
@ -27,14 +27,10 @@ For full documentation, visit [tailwindcss.com](https://tailwindcss.com).
## Community
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
For help, discussion about best practices, or feature ideas:
[Discuss Tailwind CSS on GitHub](https://github.com/tailwindcss/tailwindcss/discussions)
For chatting with others using the framework:
[Join the Tailwind CSS Discord Server](https://discord.gg/7NF8GNe)
[Discuss Tailwind CSS on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions)
## Contributing
If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindcss/tailwindcss/blob/next/.github/CONTRIBUTING.md) **before submitting a pull request**.
If you're interested in contributing to Tailwind CSS, please read our [contributing docs](https://github.com/tailwindlabs/tailwindcss/blob/main/.github/CONTRIBUTING.md) **before submitting a pull request**.

View file

@ -0,0 +1,12 @@
[package]
name = "classification-macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
syn = "2"
quote = "1"
proc-macro2 = "1"

View file

@ -0,0 +1,253 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, punctuated::Punctuated, token::Comma, Attribute, Data, DataEnum,
DeriveInput, Expr, ExprLit, ExprRange, Ident, Lit, RangeLimits, Result, Variant,
};
/// A custom derive that supports:
///
/// - `#[bytes(…)]` for single byte literals
/// - `#[bytes_range(…)]` for inclusive byte ranges (b'a'..=b'z')
/// - `#[fallback]` for a variant that covers everything else
///
/// Example usage:
///
/// ```rust
/// use classification_macros::ClassifyBytes;
///
/// #[derive(Clone, Copy, ClassifyBytes)]
/// enum Class {
/// #[bytes(b'a', b'b', b'c')]
/// Letters,
///
/// #[bytes_range(b'0'..=b'9')]
/// Digits,
///
/// #[fallback]
/// Other,
/// }
/// ```
/// Then call `b'a'.into()` to get `Example::SomeLetters`.
#[proc_macro_derive(ClassifyBytes, attributes(bytes, bytes_range, fallback))]
pub fn classify_bytes_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
// This derive only works on an enum
let Data::Enum(DataEnum { variants, .. }) = &ast.data else {
return syn::Error::new_spanned(
&ast.ident,
"ClassifyBytes can only be derived on an enum.",
)
.to_compile_error()
.into();
};
let enum_name = &ast.ident;
let mut byte_map: [Option<Ident>; 256] = [const { None }; 256];
let mut fallback_variant: Option<Ident> = None;
// Start parsing the variants
for variant in variants {
let variant_ident = &variant.ident;
// If this variant has #[fallback], record it
if has_fallback_attr(variant) {
if fallback_variant.is_some() {
let err = syn::Error::new_spanned(
variant_ident,
"Multiple variants have #[fallback]. Only one allowed.",
);
return err.to_compile_error().into();
}
fallback_variant = Some(variant_ident.clone());
}
// Get #[bytes(…)]
let single_bytes = get_bytes_attrs(&variant.attrs);
// Get #[bytes_range(…)]
let range_bytes = get_bytes_range_attrs(&variant.attrs);
// Combine them
let all_bytes = single_bytes
.into_iter()
.chain(range_bytes)
.collect::<Vec<_>>();
// Mark them in the table
for b in all_bytes {
byte_map[b as usize] = Some(variant_ident.clone());
}
}
// If no fallback variant is found, default to "Other"
let fallback_ident = fallback_variant.expect("A variant marked with #[fallback] is missing");
// For each of the 256 byte values, fill the table
let fill = byte_map
.clone()
.into_iter()
.map(|variant_opt| match variant_opt {
Some(ident) => quote!(#enum_name::#ident),
None => quote!(#enum_name::#fallback_ident),
});
// Generate the final expanded code
let expanded = quote! {
impl #enum_name {
pub const TABLE: [#enum_name; 256] = [
#(#fill),*
];
}
impl From<u8> for #enum_name {
fn from(byte: u8) -> Self {
#enum_name::TABLE[byte as usize]
}
}
impl From<&u8> for #enum_name {
fn from(byte: &u8) -> Self {
#enum_name::TABLE[*byte as usize]
}
}
};
TokenStream::from(expanded)
}
/// Checks if a variant has `#[fallback]`
fn has_fallback_attr(variant: &Variant) -> bool {
variant
.attrs
.iter()
.any(|attr| attr.path().is_ident("fallback"))
}
/// Get all single byte literals from `#[bytes(…)]`
fn get_bytes_attrs(attrs: &[Attribute]) -> Vec<u8> {
let mut assigned = Vec::new();
for attr in attrs {
if attr.path().is_ident("bytes") {
match parse_bytes_attr(attr) {
Ok(list) => assigned.extend(list),
Err(e) => panic!("Error parsing #[bytes(...)]: {}", e),
}
}
}
assigned
}
/// Parse `#[bytes(...)]` as a comma-separated list of **byte literals**, e.g. `b'a'`, `b'\n'`.
fn parse_bytes_attr(attr: &Attribute) -> Result<Vec<u8>> {
// We'll parse it as a list of syn::Lit separated by commas: e.g. (b'a', b'b')
let items: Punctuated<Lit, Comma> = attr.parse_args_with(Punctuated::parse_terminated)?;
let mut out = Vec::new();
for lit in items {
match lit {
Lit::Byte(lb) => out.push(lb.value()),
_ => {
return Err(syn::Error::new_spanned(
lit,
"Expected a byte literal like b'a'",
))
}
}
}
Ok(out)
}
/// Get all byte ranges from `#[bytes_range(...)]`
fn get_bytes_range_attrs(attrs: &[Attribute]) -> Vec<u8> {
let mut assigned = Vec::new();
for attr in attrs {
if attr.path().is_ident("bytes_range") {
match parse_bytes_range_attr(attr) {
Ok(list) => assigned.extend(list),
Err(e) => panic!("Error parsing #[bytes_range(...)]: {}", e),
}
}
}
assigned
}
/// Parse `#[bytes_range(...)]` as a comma-separated list of range expressions, e.g.:
/// `b'a'..=b'z', b'0'..=b'9'`
fn parse_bytes_range_attr(attr: &Attribute) -> Result<Vec<u8>> {
// We'll parse each element as a syn::Expr, then see if it's an Expr::Range
let exprs: Punctuated<Expr, Comma> = attr.parse_args_with(Punctuated::parse_terminated)?;
let mut out = Vec::new();
for expr in exprs {
if let Expr::Range(ExprRange {
start: Some(start),
end: Some(end),
limits,
..
}) = expr
{
let from = extract_byte_literal(&start)?;
let to = extract_byte_literal(&end)?;
match limits {
RangeLimits::Closed(_) => {
// b'a'..=b'z'
if from <= to {
out.extend(from..=to);
}
}
RangeLimits::HalfOpen(_) => {
// b'a'..b'z' => from..(to-1)
if from < to {
out.extend(from..to);
}
}
}
} else {
return Err(syn::Error::new_spanned(
expr,
"Expected a byte range like b'a'..=b'z'",
));
}
}
Ok(out)
}
/// Extract a u8 from an expression that can be:
///
/// - `Expr::Lit(Lit::Byte(...))`, e.g. b'a'
/// - `Expr::Lit(Lit::Int(...))`, e.g. 0x80 or 255
fn extract_byte_literal(expr: &Expr) -> Result<u8> {
if let Expr::Lit(ExprLit { lit, .. }) = expr {
match lit {
// Existing case: b'a'
Lit::Byte(lb) => Ok(lb.value()),
// New case: 0x80, 255, etc.
Lit::Int(li) => {
let value = li.base10_parse::<u64>()?;
if value <= 255 {
Ok(value as u8)
} else {
Err(syn::Error::new_spanned(
li,
format!("Integer literal {} out of range for a byte (0..255)", value),
))
}
}
_ => Err(syn::Error::new_spanned(
lit,
"Expected b'...' or an integer literal in range 0..=255",
)),
}
} else {
Err(syn::Error::new_spanned(
expr,
"Expected a literal expression like b'a' or 0x80",
))
}
}

3
crates/ignore/COPYING Normal file
View file

@ -0,0 +1,3 @@
This project is dual-licensed under the Unlicense and MIT licenses.
You may use this code under the terms of either license.

50
crates/ignore/Cargo.toml Normal file
View file

@ -0,0 +1,50 @@
[package]
name = "ignore"
version = "0.4.33" #:version
authors = ["Andrew Gallant <jamslam@gmail.com>"]
description = """
A fast library for efficiently matching ignore files such as `.gitignore`
against file paths.
"""
documentation = "https://docs.rs/ignore"
homepage = "https://github.com/BurntSushi/ripgrep/tree/master/crates/ignore"
repository = "https://github.com/BurntSushi/ripgrep/tree/master/crates/ignore"
readme = "README.md"
keywords = ["glob", "ignore", "gitignore", "pattern", "file"]
license = "Unlicense OR MIT"
# CHANGED: Use an explicit edition instead of `edition.workspace = true` since this crate is
# vendored into the Tailwind CSS workspace.
edition = "2024"
rust-version = "1.88"
[lib]
name = "ignore"
bench = false
[dependencies]
crossbeam-deque = "0.8.3"
# CHANGED: Use the published globset crate instead of a path dependency.
globset = "0.4.20"
log = "0.4.20"
memchr = "2.6.3"
same-file = "1.0.6"
walkdir = "2.4.0"
# CHANGED: Added `dunce` to canonicalize paths without UNC prefixes on Windows.
dunce = "1.0.5"
[dependencies.regex-automata]
version = "0.4.18"
default-features = false
features = ["std", "perf", "syntax", "meta", "nfa", "hybrid", "dfa-onepass"]
[target.'cfg(windows)'.dependencies.winapi-util]
version = "0.1.2"
[dev-dependencies]
bstr = { version = "1.6.2", default-features = false, features = ["std"] }
crossbeam-channel = "0.5.15"
[features]
# DEPRECATED. It is a no-op. SIMD is done automatically through runtime
# dispatch.
simd-accel = []

21
crates/ignore/LICENSE-MIT Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Andrew Gallant
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

59
crates/ignore/README.md Normal file
View file

@ -0,0 +1,59 @@
ignore
======
The ignore crate provides a fast recursive directory iterator that respects
various filters such as globs, file types and `.gitignore` files. This crate
also provides lower level direct access to gitignore and file type matchers.
[![Build status](https://github.com/BurntSushi/ripgrep/workflows/ci/badge.svg)](https://github.com/BurntSushi/ripgrep/actions)
[![](https://img.shields.io/crates/v/ignore.svg)](https://crates.io/crates/ignore)
Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org/).
### Documentation
[https://docs.rs/ignore](https://docs.rs/ignore)
### Usage
Add this to your `Cargo.toml`:
```toml
[dependencies]
ignore = "0.4"
```
### Example
This example shows the most basic usage of this crate. This code will
recursively traverse the current directory while automatically filtering out
files and directories according to ignore globs found in files like
`.ignore` and `.gitignore`:
```rust,no_run
use ignore::Walk;
for result in Walk::new("./") {
// Each item yielded by the iterator is either a directory entry or an
// error, so either print the path or the error.
match result {
Ok(entry) => println!("{}", entry.path().display()),
Err(err) => println!("ERROR: {}", err),
}
}
```
### Example: advanced
By default, the recursive directory iterator will ignore hidden files and
directories. This can be disabled by building the iterator with `WalkBuilder`:
```rust,no_run
use ignore::WalkBuilder;
for result in WalkBuilder::new("./").hidden(false).build() {
println!("{:?}", result);
}
```
See the documentation for `WalkBuilder` for many other options.

24
crates/ignore/UNLICENSE Normal file
View file

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

View file

@ -0,0 +1,64 @@
use std::{env, io::Write, path::Path};
use {bstr::ByteVec, ignore::WalkBuilder, walkdir::WalkDir};
fn main() {
let mut path = env::args().nth(1).unwrap();
let mut parallel = false;
let mut simple = false;
let (tx, rx) = crossbeam_channel::bounded::<DirEntry>(100);
if path == "parallel" {
path = env::args().nth(2).unwrap();
parallel = true;
} else if path == "walkdir" {
path = env::args().nth(2).unwrap();
simple = true;
}
let stdout_thread = std::thread::spawn(move || {
let mut stdout = std::io::BufWriter::new(std::io::stdout());
for dent in rx {
stdout.write_all(&Vec::from_path_lossy(dent.path())).unwrap();
stdout.write_all(b"\n").unwrap();
}
});
if parallel {
let walker = WalkBuilder::new(path).threads(6).build_parallel();
walker.run(|| {
let tx = tx.clone();
Box::new(move |result| {
use ignore::WalkState::*;
tx.send(DirEntry::Y(result.unwrap())).unwrap();
Continue
})
});
} else if simple {
let walker = WalkDir::new(path);
for result in walker {
tx.send(DirEntry::X(result.unwrap())).unwrap();
}
} else {
let walker = WalkBuilder::new(path).build();
for result in walker {
tx.send(DirEntry::Y(result.unwrap())).unwrap();
}
}
drop(tx);
stdout_thread.join().unwrap();
}
enum DirEntry {
X(walkdir::DirEntry),
Y(ignore::DirEntry),
}
impl DirEntry {
fn path(&self) -> &Path {
match *self {
DirEntry::X(ref x) => x.path(),
DirEntry::Y(ref y) => y.path(),
}
}
}

View file

@ -0,0 +1,378 @@
/// This list represents the default file types that ripgrep ships with. In
/// general, any file format is fair game, although it should generally be
/// limited to reasonably popular open formats. For other cases, you can add
/// types to each invocation of ripgrep with the '--type-add' flag.
///
/// If you would like to add or improve this list, please file a PR:
/// <https://github.com/BurntSushi/ripgrep>.
///
/// Please try to keep this list sorted lexicographically and wrapped to 79
/// columns (inclusive).
#[rustfmt::skip]
pub(crate) const DEFAULT_TYPES: &[(&[&str], &[&str])] = &[
(&["ada"], &["*.adb", "*.ads"]),
(&["agda"], &["*.agda", "*.lagda"]),
(&["aidl"], &["*.aidl"]),
(&["alire"], &["alire.toml"]),
(&["amake"], &["*.mk", "*.bp"]),
(&["asciidoc"], &["*.adoc", "*.asc", "*.asciidoc"]),
(&["asm"], &["*.asm", "*.s", "*.S"]),
(&["asp"], &[
"*.aspx", "*.aspx.cs", "*.aspx.vb", "*.ascx", "*.ascx.cs",
"*.ascx.vb", "*.asp"
]),
(&["ats"], &["*.ats", "*.dats", "*.sats", "*.hats"]),
(&["avro"], &["*.avdl", "*.avpr", "*.avsc"]),
(&["awk"], &["*.awk"]),
(&["bat", "batch"], &["*.bat"]),
(&["bazel"], &[
"*.bazel", "*.bzl", "*.BUILD", "*.bazelrc", "BUILD", "MODULE.bazel",
"WORKSPACE", "WORKSPACE.bazel", "WORKSPACE.bzlmod",
]),
(&["bitbake"], &["*.bb", "*.bbappend", "*.bbclass", "*.conf", "*.inc"]),
(&["boxlang"], &["*.bx", "*.bxm", "*.bxs"]),
(&["brotli"], &["*.br"]),
(&["buildstream"], &["*.bst"]),
(&["bzip2"], &["*.bz2", "*.tbz2"]),
(&["c"], &["*.[chH]", "*.[chH].in", "*.cats"]),
(&["cabal"], &["*.cabal"]),
(&["candid"], &["*.did"]),
(&["carp"], &["*.carp"]),
(&["cbor"], &["*.cbor"]),
(&["ceylon"], &["*.ceylon"]),
(&["cfml"], &["*.cfc", "*.cfm"]),
(&["clojure"], &["*.clj", "*.cljc", "*.cljs", "*.cljx"]),
(&["cmake"], &["*.cmake", "CMakeLists.txt"]),
(&["cmd"], &["*.bat", "*.cmd"]),
(&["cml"], &["*.cml"]),
(&["coffeescript"], &["*.coffee"]),
(&["config"], &["*.cfg", "*.conf", "*.config", "*.ini"]),
(&["container"], &["*Containerfile*", "*Dockerfile*"]),
(&["coq"], &["*.v"]),
(&["cpp"], &[
"*.[ChH]", "*.cc", "*.[ch]pp", "*.[ch]xx", "*.hh", "*.inl",
"*.[ChH].in", "*.cc.in", "*.[ch]pp.in", "*.[ch]xx.in", "*.hh.in",
]),
(&["creole"], &["*.creole"]),
(&["crystal"], &["Projectfile", "*.cr", "*.ecr", "shard.yml"]),
(&["cs"], &["*.cs"]),
(&["csharp"], &["*.cs"]),
(&["cshtml"], &["*.cshtml"]),
(&["csproj"], &["*.csproj"]),
(&["css"], &["*.css", "*.scss"]),
(&["csv"], &["*.csv"]),
(&["cuda"], &["*.cu", "*.cuh"]),
(&["cython"], &["*.pyx", "*.pxi", "*.pxd"]),
(&["d"], &["*.d"]),
(&["dart"], &["*.dart"]),
(&["devicetree"], &["*.dts", "*.dtsi", "*.dtso"]),
(&["dhall"], &["*.dhall"]),
(&["diff"], &["*.patch", "*.diff"]),
(&["dita"], &["*.dita", "*.ditamap", "*.ditaval"]),
(&["docker"], &["*Dockerfile*"]),
(&["dockercompose"], &["docker-compose.yml", "docker-compose.*.yml"]),
(&["dts"], &["*.dts", "*.dtsi"]),
(&["dvc"], &["Dvcfile", "*.dvc"]),
(&["ebuild"], &["*.ebuild", "*.eclass"]),
(&["edn"], &["*.edn"]),
(&["elisp"], &["*.el"]),
(&["elixir"], &["*.ex", "*.eex", "*.exs", "*.heex", "*.leex", "*.livemd"]),
(&["elm"], &["*.elm"]),
(&["erb"], &["*.erb"]),
(&["erlang"], &["*.erl", "*.hrl"]),
(&["fennel"], &["*.fnl"]),
(&["fidl"], &["*.fidl"]),
(&["fish"], &["*.fish"]),
(&["flatbuffers"], &["*.fbs"]),
(&["fortran"], &[
"*.f", "*.F", "*.f77", "*.F77", "*.pfo",
"*.f90", "*.F90", "*.f95", "*.F95",
]),
(&["fsharp"], &["*.fs", "*.fsx", "*.fsi"]),
(&["fut"], &["*.fut"]),
(&["gap"], &["*.g", "*.gap", "*.gi", "*.gd", "*.tst"]),
(&["gdscript"], &["*.gd"]),
(&["gleam"], &["*.gleam"]),
(&["gn"], &["*.gn", "*.gni"]),
(&["go"], &["*.go"]),
(&["gprbuild"], &["*.gpr"]),
(&["gradle"], &[
"*.gradle", "*.gradle.kts", "gradle.properties", "gradle-wrapper.*",
"gradlew", "gradlew.bat",
]),
(&["graphql"], &["*.graphql", "*.graphqls"]),
(&["groovy"], &["*.groovy", "*.gradle"]),
(&["gzip"], &["*.gz", "*.tgz"]),
(&["h"], &["*.h", "*.hh", "*.hpp"]),
(&["haml"], &["*.haml"]),
(&["hare"], &["*.ha"]),
(&["haskell"], &["*.hs", "*.lhs", "*.cpphs", "*.c2hs", "*.hsc"]),
(&["hbs"], &["*.hbs"]),
(&["hs"], &["*.hs", "*.lhs"]),
(&["html"], &["*.htm", "*.html", "*.ejs"]),
(&["hurl"], &["*.hurl"]),
(&["hy"], &["*.hy"]),
(&["idris"], &["*.idr", "*.lidr"]),
(&["janet"], &["*.janet"]),
(&["java"], &["*.java", "*.jsp", "*.jspx", "*.properties"]),
(&["jinja"], &["*.j2", "*.jinja", "*.jinja2"]),
(&["jl"], &["*.jl"]),
(&["js"], &["*.js", "*.jsx", "*.vue", "*.cjs", "*.mjs"]),
(&["json"], &["*.json", "composer.lock", "*.sarif"]),
(&["jsonl"], &["*.jsonl"]),
(&["julia"], &["*.jl"]),
(&["jupyter"], &["*.ipynb", "*.jpynb"]),
(&["k"], &["*.k"]),
(&["kconfig"], &["Kconfig", "Kconfig.*"]),
(&["kotlin"], &["*.kt", "*.kts"]),
(&["lean"], &["*.lean"]),
(&["less"], &["*.less"]),
(&["license"], &[
// General
"COPYING", "COPYING[.-]*",
"COPYRIGHT", "COPYRIGHT[.-]*",
"EULA", "EULA[.-]*",
"licen[cs]e", "licen[cs]e.*",
"LICEN[CS]E", "LICEN[CS]E[.-]*", "*[.-]LICEN[CS]E*",
"NOTICE", "NOTICE[.-]*",
"PATENTS", "PATENTS[.-]*",
"UNLICEN[CS]E", "UNLICEN[CS]E[.-]*",
// GPL (gpl.txt, etc.)
"agpl[.-]*",
"gpl[.-]*",
"lgpl[.-]*",
// Other license-specific (APACHE-2.0.txt, etc.)
"AGPL-*[0-9]*",
"APACHE-*[0-9]*",
"BSD-*[0-9]*",
"CC-BY-*",
"GFDL-*[0-9]*",
"GNU-*[0-9]*",
"GPL-*[0-9]*",
"LGPL-*[0-9]*",
"MIT-*[0-9]*",
"MPL-*[0-9]*",
"OFL-*[0-9]*",
]),
(&["lilypond"], &["*.ly", "*.ily"]),
(&["lisp"], &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
(&["llvm"], &["*.ll"]),
(&["lock"], &["*.lock", "package-lock.json"]),
(&["log"], &["*.log"]),
(&["lua"], &["*.lua"]),
(&["lz4"], &["*.lz4"]),
(&["lzma"], &["*.lzma"]),
(&["m4"], &["*.ac", "*.m4"]),
(&["make"], &[
"[Gg][Nn][Uu]makefile", "[Mm]akefile",
"[Gg][Nn][Uu]makefile.am", "[Mm]akefile.am",
"[Gg][Nn][Uu]makefile.in", "[Mm]akefile.in",
"Makefile.*",
"*.mk", "*.mak"
]),
(&["mako"], &["*.mako", "*.mao"]),
(&["man"], &["*.[0-9lnpx]", "*.[0-9][cEFMmpSx]"]),
(&["markdown", "md"], &[
"*.markdown",
"*.md",
"*.mdown",
"*.mdwn",
"*.mkd",
"*.mkdn",
"*.mdx",
]),
(&["matlab"], &["*.m"]),
(&["meson"], &["meson.build", "meson_options.txt", "meson.options"]),
(&["minified"], &["*.min.html", "*.min.css", "*.min.js"]),
(&["mint"], &["*.mint"]),
(&["mk"], &["mkfile"]),
(&["ml"], &["*.ml"]),
(&["mojo"], &["*.mojo"]),
(&["motoko"], &["*.mo"]),
(&["msbuild"], &[
"*.csproj", "*.fsproj", "*.vcxproj", "*.proj", "*.props", "*.targets",
"*.sln", "*.slnf"
]),
(&["nim"], &["*.nim", "*.nimf", "*.nimble", "*.nims"]),
(&["nix"], &["*.nix"]),
(&["objc"], &["*.h", "*.m"]),
(&["objcpp"], &["*.h", "*.mm"]),
(&["ocaml"], &["*.ml", "*.mli", "*.mll", "*.mly"]),
(&["org"], &["*.org", "*.org_archive"]),
(&["pants"], &["BUILD"]),
(&["pascal"], &["*.pas", "*.dpr", "*.lpr", "*.pp", "*.inc"]),
(&["pdf"], &["*.pdf"]),
(&["perl"], &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm", "*.t"]),
(&["php"], &[
// note that PHP 6 doesn't exist
// See: https://wiki.php.net/rfc/php6
"*.php", "*.php3", "*.php4", "*.php5", "*.php7", "*.php8",
"*.pht", "*.phtml"
]),
(&["pkgbuild"], &["PKGBUILD"]),
(&["po"], &["*.po"]),
(&["pod"], &["*.pod"]),
(&["postscript"], &["*.eps", "*.ps"]),
(&["prolog"], &["*.pl", "*.pro", "*.prolog", "*.P"]),
(&["proto", "protobuf"], &["*.proto"]),
(&["ps"], &["*.cdxml", "*.ps1", "*.ps1xml", "*.psd1", "*.psm1"]),
(&["puppet"], &["*.epp", "*.erb", "*.pp", "*.rb"]),
(&["purs"], &["*.purs"]),
(&["py", "python"], &["*.py", "*.pyi"]),
(&["qmake"], &["*.pro", "*.pri", "*.prf"]),
(&["qml"], &["*.qml"]),
(&["qrc"], &["*.qrc"]),
(&["qui"], &["*.ui"]),
(&["r"], &["*.R", "*.r", "*.Rmd", "*.rmd", "*.Rnw", "*.rnw"]),
(&["racket"], &["*.rkt"]),
(&["raku"], &[
"*.raku", "*.rakumod", "*.rakudoc", "*.rakutest",
"*.p6", "*.pl6", "*.pm6"
]),
(&["rdoc"], &["*.rdoc"]),
(&["readme"], &["README*", "*README"]),
(&["reasonml"], &["*.re", "*.rei"]),
(&["red"], &["*.r", "*.red", "*.reds"]),
(&["rescript"], &["*.res", "*.resi"]),
(&["robot"], &["*.robot"]),
(&["rocq"], &["*.v"]),
(&["rst"], &["*.rst"]),
(&["ruby"], &[
// Idiomatic files
"config.ru", "Gemfile", ".irbrc", "Rakefile",
// Extensions
"*.gemspec", "*.rb", "*.rbw", "*.rake"
]),
(&["rust"], &["*.rs"]),
(&["sass"], &["*.sass", "*.scss"]),
(&["scala"], &["*.scala", "*.sbt"]),
(&["scdoc"], &["*.scd", "*.scdoc"]),
(&["seed7"], &["*.sd7", "*.s7i"]),
(&["sh"], &[
// Portable/misc. init files
".env", ".login", ".logout", ".profile", "profile",
// bash-specific init files
".bash_login", "bash_login",
".bash_logout", "bash_logout",
".bash_profile", "bash_profile",
".bashrc", "bashrc", "*.bashrc",
// csh-specific init files
".cshrc", "*.cshrc",
// ksh-specific init files
".kshrc", "*.kshrc",
// tcsh-specific init files
".tcshrc",
// zsh-specific init files
".zshenv", "zshenv",
".zlogin", "zlogin",
".zlogout", "zlogout",
".zprofile", "zprofile",
".zshrc", "zshrc",
// Extensions
"*.bash", "*.csh", "*.env", "*.ksh", "*.sh", "*.tcsh", "*.zsh",
]),
(&["slim"], &["*.skim", "*.slim", "*.slime"]),
(&["smarty"], &["*.tpl"]),
(&["sml"], &["*.sml", "*.sig"]),
(&["solidity"], &["*.sol"]),
(&["soy"], &["*.soy"]),
(&["spark"], &["*.spark"]),
(&["spec"], &["*.spec"]),
(&["sql"], &["*.sql", "*.psql"]),
(&["ssa"], &["*.ssa"]),
(&["stylus"], &["*.styl"]),
(&["sv"], &["*.v", "*.vg", "*.sv", "*.svh", "*.h"]),
(&["svelte"], &["*.svelte", "*.svelte.ts"]),
(&["svg"], &["*.svg"]),
(&["swift"], &["*.swift"]),
(&["swig"], &["*.def", "*.i"]),
(&["systemd"], &[
"*.automount", "*.conf", "*.device", "*.link", "*.mount", "*.path",
"*.scope", "*.service", "*.slice", "*.socket", "*.swap", "*.target",
"*.timer",
]),
(&["taskpaper"], &["*.taskpaper"]),
(&["tcl"], &["*.tcl"]),
(&["tex"], &["*.tex", "*.ltx", "*.cls", "*.sty", "*.bib", "*.dtx", "*.ins"]),
(&["texinfo"], &["*.texi"]),
(&["textile"], &["*.textile"]),
(&["tf"], &[
"*.tf", "*.tf.json", "*.tfvars", "*.tfvars.json",
"*.terraformrc", "terraform.rc", "*.tfrc", "*.terraform.lock.hcl",
]),
(&["thrift"], &["*.thrift"]),
(&["toml"], &["*.toml", "Cargo.lock"]),
(&["ts", "typescript"], &["*.ts", "*.tsx", "*.cts", "*.mts"]),
(&["twig"], &["*.twig"]),
(&["txt"], &["*.txt"]),
(&["typoscript"], &["*.typoscript", "*.ts"]),
(&["typst"], &["*.typ"]),
(&["usd"], &["*.usd", "*.usda", "*.usdc"]),
(&["v"], &["*.v", "*.vsh"]),
(&["vala"], &["*.vala"]),
(&["vb"], &["*.vb"]),
(&["vcl"], &["*.vcl"]),
(&["verilog"], &["*.v", "*.vh", "*.sv", "*.svh"]),
(&["vhdl"], &["*.vhd", "*.vhdl"]),
(&["vim"], &[
"*.vim", ".vimrc", ".gvimrc", "vimrc", "gvimrc", "_vimrc", "_gvimrc",
]),
(&["vimscript"], &[
"*.vim", ".vimrc", ".gvimrc", "vimrc", "gvimrc", "_vimrc", "_gvimrc",
]),
(&["vue"], &["*.vue"]),
(&["webidl"], &["*.idl", "*.webidl", "*.widl"]),
(&["wgsl"], &["*.wgsl"]),
(&["wiki"], &["*.mediawiki", "*.wiki"]),
(&["xml"], &[
"*.xml", "*.xml.dist", "*.dtd", "*.xsl", "*.xslt", "*.xsd", "*.xjb",
"*.rng", "*.sch", "*.xhtml",
]),
(&["xz"], &["*.xz", "*.txz"]),
(&["yacc"], &["*.y"]),
(&["yaml"], &["*.yaml", "*.yml"]),
(&["yang"], &["*.yang"]),
(&["z"], &["*.Z"]),
(&["zig"], &["*.zig"]),
(&["zsh"], &[
".zshenv", "zshenv",
".zlogin", "zlogin",
".zlogout", "zlogout",
".zprofile", "zprofile",
".zshrc", "zshrc",
"*.zsh",
]),
(&["zstd"], &["*.zst", "*.zstd"]),
];
#[cfg(test)]
mod tests {
use super::DEFAULT_TYPES;
#[test]
fn default_types_are_sorted() {
let mut names = DEFAULT_TYPES.iter().map(|(aliases, _)| aliases[0]);
let Some(mut previous_name) = names.next() else {
return;
};
for name in names {
assert!(
name > previous_name,
r#""{}" should be sorted before "{}" in `DEFAULT_TYPES`"#,
name,
previous_name
);
previous_name = name;
}
}
#[test]
fn default_types_aliases_are_sorted() {
for (aliases, _) in DEFAULT_TYPES.iter() {
assert!(
aliases.is_sorted(),
"this alias list is not sorted: {aliases:?}",
);
}
}
}

1648
crates/ignore/src/dir.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,913 @@
/*!
The gitignore module provides a way to match globs from a gitignore file
against file paths.
Note that this module implements the specification as described in the
`gitignore` man page from scratch. That is, this module does *not* shell out to
the `git` command line tool.
*/
use std::{
fs::File,
io::{BufRead, BufReader, Read},
path::{Path, PathBuf},
sync::Arc,
};
use {
globset::{Candidate, GlobBuilder, GlobSet, GlobSetBuilder},
regex_automata::util::pool::Pool,
};
use crate::{
Error, Match, PartialErrorBuilder,
pathutil::{is_file_name, strip_prefix},
};
/// Glob represents a single glob in a gitignore file.
///
/// This is used to report information about the highest precedent glob that
/// matched in one or more gitignore files.
#[derive(Clone, Debug)]
pub struct Glob {
/// The file path that this glob was extracted from.
from: Option<PathBuf>,
/// The original glob string.
original: String,
/// The actual glob string used to convert to a regex.
actual: String,
/// Whether this is a whitelisted glob or not.
is_whitelist: bool,
/// Whether this glob should only match directories or not.
is_only_dir: bool,
}
impl Glob {
/// Returns the file path that defined this glob.
pub fn from(&self) -> Option<&Path> {
self.from.as_ref().map(|p| &**p)
}
/// The original glob as it was defined in a gitignore file.
pub fn original(&self) -> &str {
&self.original
}
/// The actual glob that was compiled to respect gitignore
/// semantics.
pub fn actual(&self) -> &str {
&self.actual
}
/// Whether this was a whitelisted glob or not.
pub fn is_whitelist(&self) -> bool {
self.is_whitelist
}
/// Whether this glob must match a directory or not.
pub fn is_only_dir(&self) -> bool {
self.is_only_dir
}
/// Returns true if and only if this glob has a `**/` prefix.
fn has_doublestar_prefix(&self) -> bool {
self.actual.starts_with("**/") || self.actual == "**"
}
}
/// Gitignore is a matcher for the globs in one or more gitignore files
/// in the same directory.
#[derive(Clone, Debug)]
pub struct Gitignore {
set: GlobSet,
root: PathBuf,
globs: Vec<Glob>,
num_ignores: u64,
num_whitelists: u64,
matches: Option<Arc<Pool<Vec<usize>>>>,
// CHANGED: Add a flag to have Gitignore rules that apply only to files.
only_on_files: bool,
}
impl Gitignore {
/// Creates a new gitignore matcher from the gitignore file path given.
///
/// If it's desirable to include multiple gitignore files in a single
/// matcher, or read gitignore globs from a different source, then
/// use `GitignoreBuilder`.
///
/// This always returns a valid matcher, even if it's empty. In particular,
/// a Gitignore file can be partially valid, e.g., when one glob is invalid
/// but the rest aren't.
///
/// Note that I/O errors are ignored. For more granular control over
/// errors, use `GitignoreBuilder`.
pub fn new<P: AsRef<Path>>(
gitignore_path: P,
) -> (Gitignore, Option<Error>) {
let path = gitignore_path.as_ref();
let parent = path.parent().unwrap_or(Path::new("/"));
let mut builder = GitignoreBuilder::new(parent);
let mut errs = PartialErrorBuilder::default();
errs.maybe_push_ignore_io(builder.add(path));
match builder.build() {
Ok(gi) => (gi, errs.into_error_option()),
Err(err) => {
errs.push(err);
(Gitignore::empty(), errs.into_error_option())
}
}
}
/// Creates a new gitignore matcher from the global ignore file, if one
/// exists.
///
/// The global config file path is specified by git's `core.excludesFile`
/// config option.
///
/// # Behavior
///
/// This routine does its best to discover any global git exclude files.
/// This will try to parse out the `excludesFile` config option in your
/// global git configuration, if necessary.
///
/// The specific things this routine tries (which are subject to change
/// based on how git behaves) are:
///
///
///
/// Git's config file location is `$HOME/.gitconfig`. If `$HOME/.gitconfig`
/// does not exist or does not specify `core.excludesFile`, then
/// `$XDG_CONFIG_HOME/git/ignore` is read. If `$XDG_CONFIG_HOME` is not
/// set or is empty, then `$HOME/.config/git/ignore` is used instead.
pub fn global() -> (Gitignore, Option<Error>) {
match std::env::current_dir() {
Ok(cwd) => GitignoreBuilder::new(cwd).build_global(),
Err(err) => (Gitignore::empty(), Some(err.into())),
}
}
/// Creates a new empty gitignore matcher that never matches anything.
///
/// Its path is empty.
pub fn empty() -> Gitignore {
Gitignore {
set: GlobSet::empty(),
root: PathBuf::from(""),
globs: vec![],
num_ignores: 0,
num_whitelists: 0,
matches: None,
// CHANGED: Add a flag to have Gitignore rules that apply only to
// files.
only_on_files: false,
}
}
/// Returns the directory containing this gitignore matcher.
///
/// All matches are done relative to this path.
pub fn path(&self) -> &Path {
&*self.root
}
/// Returns true if and only if this gitignore has zero globs, and
/// therefore never matches any file path.
pub fn is_empty(&self) -> bool {
self.set.is_empty()
}
/// Returns the total number of globs, which should be equivalent to
/// `num_ignores + num_whitelists`.
pub fn len(&self) -> usize {
self.set.len()
}
/// Returns the total number of ignore globs.
pub fn num_ignores(&self) -> u64 {
self.num_ignores
}
/// Returns the total number of whitelisted globs.
pub fn num_whitelists(&self) -> u64 {
self.num_whitelists
}
/// Returns whether the given path (file or directory) matched a pattern in
/// this gitignore matcher.
///
/// `is_dir` should be true if the path refers to a directory and false
/// otherwise.
///
/// The given path is matched relative to the path given when building
/// the matcher. Specifically, before matching `path`, its prefix (as
/// determined by a common suffix of the directory containing this
/// gitignore) is stripped. If there is no common suffix/prefix overlap,
/// then `path` is assumed to be relative to this matcher.
pub fn matched<P: AsRef<Path>>(
&self,
path: P,
is_dir: bool,
) -> Match<&Glob> {
if self.is_empty() {
return Match::None;
}
self.matched_stripped(self.strip(path.as_ref()), is_dir)
}
/// Returns whether the given path (file or directory, and expected to be
/// under the root) or any of its parent directories (up to the root)
/// matched a pattern in this gitignore matcher.
///
/// NOTE: This method is more expensive than walking the directory hierarchy
/// top-to-bottom and matching the entries. But, is easier to use in cases
/// when a list of paths are available without a hierarchy.
///
/// `is_dir` should be true if the path refers to a directory and false
/// otherwise.
///
/// The given path is matched relative to the path given when building
/// the matcher. Specifically, before matching `path`, its prefix (as
/// determined by a common suffix of the directory containing this
/// gitignore) is stripped. If there is no common suffix/prefix overlap,
/// then `path` is assumed to be relative to this matcher.
///
/// # Panics
///
/// This method panics if the given file path is not under the root path
/// of this matcher.
pub fn matched_path_or_any_parents<P: AsRef<Path>>(
&self,
path: P,
is_dir: bool,
) -> Match<&Glob> {
if self.is_empty() {
return Match::None;
}
let mut path = self.strip(path.as_ref());
assert!(!path.has_root(), "path is expected to be under the root");
match self.matched_stripped(path, is_dir) {
Match::None => (), // walk up
a_match => return a_match,
}
while let Some(parent) = path.parent() {
match self.matched_stripped(parent, /* is_dir */ true) {
Match::None => path = parent, // walk up
a_match => return a_match,
}
}
Match::None
}
/// Like matched, but takes a path that has already been stripped.
fn matched_stripped<P: AsRef<Path>>(
&self,
path: P,
is_dir: bool,
) -> Match<&Glob> {
if self.is_empty() {
return Match::None;
}
// CHANGED: Rules marked as only_on_files can not match against
// directories.
if self.only_on_files && is_dir {
return Match::None;
}
let path = path.as_ref();
let mut matches = self.matches.as_ref().unwrap().get();
let candidate = Candidate::new(path);
self.set.matches_candidate_into(&candidate, &mut *matches);
for &i in matches.iter().rev() {
let glob = &self.globs[i];
if !glob.is_only_dir() || is_dir {
return if glob.is_whitelist() {
Match::Whitelist(glob)
} else {
Match::Ignore(glob)
};
}
}
Match::None
}
/// Strips the given path such that it's suitable for matching with this
/// gitignore matcher.
fn strip<'a, P: 'a + AsRef<Path> + ?Sized>(
&'a self,
path: &'a P,
) -> &'a Path {
let mut path = path.as_ref();
// A leading ./ is completely superfluous. We also strip it from
// our gitignore root path, so we need to strip it from our candidate
// path too.
if let Some(p) = strip_prefix("./", path) {
path = p;
}
// Strip any common prefix between the candidate path and the root
// of the gitignore, to make sure we get relative matching right.
// BUT, a file name might not have any directory components to it,
// in which case, we don't want to accidentally strip any part of the
// file name.
//
// As an additional special case, if the root is just `.`, then we
// shouldn't try to strip anything, e.g., when path begins with a `.`.
if self.root != Path::new(".") && !is_file_name(path) {
if let Some(p) = strip_prefix(&self.root, path) {
path = p;
// If we're left with a leading slash, get rid of it.
if let Some(p) = strip_prefix("/", path) {
path = p;
}
}
}
path
}
}
/// Builds a matcher for a single set of globs from a .gitignore file.
#[derive(Clone, Debug)]
pub struct GitignoreBuilder {
builder: GlobSetBuilder,
root: PathBuf,
globs: Vec<Glob>,
case_insensitive: bool,
allow_unclosed_class: bool,
// CHANGED: Add a flag to have Gitignore rules that apply only to files.
only_on_files: bool,
}
impl GitignoreBuilder {
/// Create a new builder for a gitignore file.
///
/// The path given should be the path at which the globs for this gitignore
/// file should be matched. Note that paths are always matched relative
/// to the root path given here. Generally, the root path should correspond
/// to the *directory* containing a `.gitignore` file.
pub fn new<P: AsRef<Path>>(root: P) -> GitignoreBuilder {
let root = root.as_ref();
GitignoreBuilder {
builder: GlobSetBuilder::new(),
root: strip_prefix("./", root).unwrap_or(root).to_path_buf(),
globs: vec![],
case_insensitive: false,
allow_unclosed_class: true,
// CHANGED: Add a flag to have Gitignore rules that apply only to
// files.
only_on_files: false,
}
}
/// Builds a new matcher from the globs added so far.
///
/// Once a matcher is built, no new globs can be added to it.
pub fn build(&self) -> Result<Gitignore, Error> {
let nignore = self.globs.iter().filter(|g| !g.is_whitelist()).count();
let nwhite = self.globs.iter().filter(|g| g.is_whitelist()).count();
let set = self
.builder
.build()
.map_err(|err| Error::Glob { glob: None, err: err.to_string() })?;
Ok(Gitignore {
set,
root: self.root.clone(),
globs: self.globs.clone(),
num_ignores: nignore as u64,
num_whitelists: nwhite as u64,
matches: Some(Arc::new(
Pool::with_available_parallelism_capacity(|| vec![]),
)),
// CHANGED: Add a flag to have Gitignore rules that apply only to
// files.
only_on_files: self.only_on_files,
})
}
/// Build a global gitignore matcher using the configuration in this
/// builder.
///
/// This consumes ownership of the builder unlike `build` because it
/// must mutate the builder to add the global gitignore globs.
///
/// Note that this ignores the path given to this builder's constructor
/// and instead derives the path automatically from git's global
/// configuration.
pub fn build_global(mut self) -> (Gitignore, Option<Error>) {
match gitconfig_excludes_path() {
None => (Gitignore::empty(), None),
Some(path) => {
if !path.is_file() {
(Gitignore::empty(), None)
} else {
let mut errs = PartialErrorBuilder::default();
errs.maybe_push_ignore_io(self.add(path));
match self.build() {
Ok(gi) => (gi, errs.into_error_option()),
Err(err) => {
errs.push(err);
(Gitignore::empty(), errs.into_error_option())
}
}
}
}
}
}
/// Add each glob from the file path given.
///
/// The file given should be formatted as a `gitignore` file.
///
/// Note that partial errors can be returned. For example, if there was
/// a problem adding one glob, an error for that will be returned, but
/// all other valid globs will still be added.
pub fn add<P: AsRef<Path>>(&mut self, path: P) -> Option<Error> {
let path = path.as_ref();
let file = match File::open(path) {
Err(err) => return Some(Error::Io(err).with_path(path)),
Ok(file) => file,
};
log::debug!("opened gitignore file: {}", path.display());
let rdr = BufReader::new(file);
let mut errs = PartialErrorBuilder::default();
for (i, line) in rdr.lines().enumerate() {
let lineno = (i + 1) as u64;
let line = match line {
Ok(line) => line,
Err(err) => {
errs.push(Error::Io(err).tagged(path, lineno));
break;
}
};
// Match Git's handling of .gitignore files that begin with the Unicode BOM
const UTF8_BOM: &str = "\u{feff}";
let line =
if i == 0 { line.trim_start_matches(UTF8_BOM) } else { &line };
if let Err(err) = self.add_line(Some(path.to_path_buf()), &line) {
errs.push(err.tagged(path, lineno));
}
}
errs.into_error_option()
}
/// Add each glob line from the string given.
///
/// If this string came from a particular `gitignore` file, then its path
/// should be provided here.
///
/// The string given should be formatted as a `gitignore` file.
#[cfg(test)]
fn add_str(
&mut self,
from: Option<PathBuf>,
gitignore: &str,
) -> Result<&mut GitignoreBuilder, Error> {
for line in gitignore.lines() {
self.add_line(from.clone(), line)?;
}
Ok(self)
}
/// Add a line from a gitignore file to this builder.
///
/// If this line came from a particular `gitignore` file, then its path
/// should be provided here.
///
/// If the line could not be parsed as a glob, then an error is returned.
pub fn add_line(
&mut self,
from: Option<PathBuf>,
mut line: &str,
) -> Result<&mut GitignoreBuilder, Error> {
#![allow(deprecated)]
if line.starts_with("#") {
return Ok(self);
}
if !line.ends_with("\\ ") {
line = line.trim_right();
}
if line.is_empty() {
return Ok(self);
}
let mut glob = Glob {
from,
original: line.to_string(),
actual: String::new(),
is_whitelist: false,
is_only_dir: false,
};
let mut is_absolute = false;
if line.starts_with("\\!") || line.starts_with("\\#") {
line = &line[1..];
is_absolute = line.chars().nth(0) == Some('/');
} else {
if line.starts_with("!") {
glob.is_whitelist = true;
line = &line[1..];
}
if line.starts_with("/") {
// `man gitignore` says that if a glob starts with a slash,
// then the glob can only match the beginning of a path
// (relative to the location of gitignore). We achieve this by
// simply banning wildcards from matching /.
line = &line[1..];
is_absolute = true;
}
}
// If it ends with a slash, then this should only match directories,
// but the slash should otherwise not be used while globbing.
if line.as_bytes().last() == Some(&b'/') {
glob.is_only_dir = true;
line = &line[..line.len() - 1];
// If the slash was escaped, then remove the escape.
// See: https://github.com/BurntSushi/ripgrep/issues/2236
if line.as_bytes().last() == Some(&b'\\') {
line = &line[..line.len() - 1];
}
}
glob.actual = line.to_string();
// If there is a literal slash, then this is a glob that must match the
// entire path name. Otherwise, we should let it match anywhere, so use
// a **/ prefix.
if !is_absolute && !line.chars().any(|c| c == '/') {
// ... but only if we don't already have a **/ prefix.
if !glob.has_doublestar_prefix() {
glob.actual = format!("**/{}", glob.actual);
}
}
// If the glob ends with `/**`, then we should only match everything
// inside a directory, but not the directory itself. Standard globs
// will match the directory. So we add `/*` to force the issue.
if glob.actual.ends_with("/**") {
glob.actual = format!("{}/*", glob.actual);
}
let parsed = GlobBuilder::new(&glob.actual)
.literal_separator(true)
.case_insensitive(self.case_insensitive)
.backslash_escape(true)
.allow_unclosed_class(self.allow_unclosed_class)
.build()
.map_err(|err| Error::Glob {
glob: Some(glob.original.clone()),
err: err.kind().to_string(),
})?;
self.builder.add(parsed);
self.globs.push(glob);
Ok(self)
}
/// Toggle whether the globs should be matched case insensitively or not.
///
/// When this option is changed, only globs added after the change will be
/// affected.
///
/// This is disabled by default.
pub fn case_insensitive(
&mut self,
yes: bool,
) -> Result<&mut GitignoreBuilder, Error> {
// TODO: This should not return a `Result`. Fix this in the next semver
// release.
self.case_insensitive = yes;
Ok(self)
}
/// Toggle whether unclosed character classes are allowed. When allowed,
/// a `[` without a matching `]` is treated literally instead of resulting
/// in a parse error.
///
/// For example, if this is set then the glob `[abc` will be treated as the
/// literal string `[abc` instead of returning an error.
///
/// By default, this is true in order to match established `gitignore`
/// semantics. Generally speaking, enabling this leads to worse failure
/// modes since the glob parser becomes more permissive. You might want to
/// enable this when compatibility (e.g., with POSIX glob implementations)
/// is more important than good error messages.
pub fn allow_unclosed_class(
&mut self,
yes: bool,
) -> &mut GitignoreBuilder {
self.allow_unclosed_class = yes;
self
}
/// CHANGED: Add a flag to have Gitignore rules that apply only to files.
///
/// If this is set, then the globs will only be matched against file paths.
/// This will ensure that ignore rules like `*.pages` will _only_ ignore
/// files ending in `.pages` and not folders ending in `.pages`.
pub fn only_on_files(&mut self, yes: bool) -> &mut GitignoreBuilder {
self.only_on_files = yes;
self
}
}
/// Return the file path of the current environment's global gitignore file.
///
/// Note that the file path returned may not exist.
pub fn gitconfig_excludes_path() -> Option<PathBuf> {
// When GIT_CONFIG_GLOBAL is set, it replaces both $HOME/.gitconfig and
// $XDG_CONFIG_HOME/git/config (per git 2.32+). Otherwise, git supports
// $HOME/.gitconfig and $XDG_CONFIG_HOME/git/config simultaneously, where
// $HOME/.gitconfig takes precedent.
gitconfig_global_env_contents()
.and_then(|x| parse_excludes_file(&x))
.or_else(|| {
gitconfig_home_contents().and_then(|x| parse_excludes_file(&x))
})
.or_else(|| {
gitconfig_xdg_contents().and_then(|x| parse_excludes_file(&x))
})
// System-level config has the lowest priority for core.excludesFile.
// GIT_CONFIG_SYSTEM overrides the default /etc/gitconfig path.
.or_else(|| {
gitconfig_system_contents().and_then(|x| parse_excludes_file(&x))
})
.or_else(excludes_file_default)
}
/// Returns the file contents of git's global config file from the path
/// specified by the `GIT_CONFIG_GLOBAL` environment variable.
fn gitconfig_global_env_contents() -> Option<Vec<u8>> {
let path = std::env::var_os("GIT_CONFIG_GLOBAL").map(PathBuf::from)?;
if path.as_os_str().is_empty() {
return None;
}
let mut file = BufReader::new(File::open(path).ok()?);
let mut contents = vec![];
file.read_to_end(&mut contents).ok().map(|_| contents)
}
/// Returns the file contents of git's system-level config file.
///
/// Checks `GIT_CONFIG_SYSTEM` first, then falls back to `/etc/gitconfig`.
fn gitconfig_system_contents() -> Option<Vec<u8>> {
let path = std::env::var_os("GIT_CONFIG_SYSTEM")
.map(PathBuf::from)
.filter(|x| !x.as_os_str().is_empty())
.unwrap_or_else(|| PathBuf::from("/etc/gitconfig"));
let mut file = BufReader::new(File::open(path).ok()?);
let mut contents = vec![];
file.read_to_end(&mut contents).ok().map(|_| contents)
}
/// Returns the file contents of git's global config file, if one exists, in
/// the user's home directory.
fn gitconfig_home_contents() -> Option<Vec<u8>> {
let home = home_dir()?;
let mut file = BufReader::new(File::open(home.join(".gitconfig")).ok()?);
let mut contents = vec![];
file.read_to_end(&mut contents).ok().map(|_| contents)
}
/// Returns the file contents of git's global config file, if one exists, in
/// the user's XDG_CONFIG_HOME directory.
fn gitconfig_xdg_contents() -> Option<Vec<u8>> {
let path = std::env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.filter(|x| !x.as_os_str().is_empty())
.or_else(|| home_dir().map(|p| p.join(".config")))
.map(|x| x.join("git/config"))?;
let mut file = BufReader::new(File::open(path).ok()?);
let mut contents = vec![];
file.read_to_end(&mut contents).ok().map(|_| contents)
}
/// Returns the default file path for a global .gitignore file.
///
/// Specifically, this respects XDG_CONFIG_HOME.
fn excludes_file_default() -> Option<PathBuf> {
std::env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.filter(|x| !x.as_os_str().is_empty())
.or_else(|| home_dir().map(|p| p.join(".config")))
.map(|x| x.join("git/ignore"))
}
/// Extract git's `core.excludesfile` config setting from the raw file contents
/// given.
fn parse_excludes_file(data: &[u8]) -> Option<PathBuf> {
use std::sync::OnceLock;
use regex_automata::{meta::Regex, util::syntax};
// N.B. This is the lazy approach, and isn't technically correct, but
// probably works in more circumstances. I guess we would ideally have
// a full INI parser. Yuck.
static RE: OnceLock<Regex> = OnceLock::new();
let re = RE.get_or_init(|| {
Regex::builder()
.configure(Regex::config().utf8_empty(false))
.syntax(syntax::Config::new().utf8(false))
.build(r#"(?im-u)^\s*excludesfile\s*=\s*"?\s*(\S+?)\s*"?\s*$"#)
.unwrap()
});
// We don't care about amortizing allocs here I think. This should only
// be called ~once per traversal or so? (Although it's not guaranteed...)
let mut caps = re.create_captures();
re.captures(data, &mut caps);
let span = caps.get_group(1)?;
let candidate = &data[span];
std::str::from_utf8(candidate).ok().map(|s| PathBuf::from(expand_tilde(s)))
}
/// Expands ~ in file paths to the value of $HOME.
fn expand_tilde(path: &str) -> String {
let home = match home_dir() {
None => return path.to_string(),
Some(home) => home.to_string_lossy().into_owned(),
};
path.replace("~", &home)
}
/// Returns the location of the user's home directory.
fn home_dir() -> Option<PathBuf> {
// We're fine with using std::env::home_dir for now. Its bugs are, IMO,
// pretty minor corner cases.
#![allow(deprecated)]
std::env::home_dir()
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::{Gitignore, GitignoreBuilder};
fn gi_from_str<P: AsRef<Path>>(root: P, s: &str) -> Gitignore {
let mut builder = GitignoreBuilder::new(root);
builder.add_str(None, s).unwrap();
builder.build().unwrap()
}
macro_rules! ignored {
($name:ident, $root:expr, $gi:expr, $path:expr) => {
ignored!($name, $root, $gi, $path, false);
};
($name:ident, $root:expr, $gi:expr, $path:expr, $is_dir:expr) => {
#[test]
fn $name() {
let gi = gi_from_str($root, $gi);
assert!(gi.matched($path, $is_dir).is_ignore());
}
};
}
macro_rules! not_ignored {
($name:ident, $root:expr, $gi:expr, $path:expr) => {
not_ignored!($name, $root, $gi, $path, false);
};
($name:ident, $root:expr, $gi:expr, $path:expr, $is_dir:expr) => {
#[test]
fn $name() {
let gi = gi_from_str($root, $gi);
assert!(!gi.matched($path, $is_dir).is_ignore());
}
};
}
const ROOT: &'static str = "/home/foobar/rust/rg";
ignored!(ig1, ROOT, "months", "months");
ignored!(ig2, ROOT, "*.lock", "Cargo.lock");
ignored!(ig3, ROOT, "*.rs", "src/main.rs");
ignored!(ig4, ROOT, "src/*.rs", "src/main.rs");
ignored!(ig5, ROOT, "/*.c", "cat-file.c");
ignored!(ig6, ROOT, "/src/*.rs", "src/main.rs");
ignored!(ig7, ROOT, "!src/main.rs\n*.rs", "src/main.rs");
ignored!(ig8, ROOT, "foo/", "foo", true);
ignored!(ig9, ROOT, "**/foo", "foo");
ignored!(ig10, ROOT, "**/foo", "src/foo");
ignored!(ig11, ROOT, "**/foo/**", "src/foo/bar");
ignored!(ig12, ROOT, "**/foo/**", "wat/src/foo/bar/baz");
ignored!(ig13, ROOT, "**/foo/bar", "foo/bar");
ignored!(ig14, ROOT, "**/foo/bar", "src/foo/bar");
ignored!(ig15, ROOT, "abc/**", "abc/x");
ignored!(ig16, ROOT, "abc/**", "abc/x/y");
ignored!(ig17, ROOT, "abc/**", "abc/x/y/z");
ignored!(ig18, ROOT, "a/**/b", "a/b");
ignored!(ig19, ROOT, "a/**/b", "a/x/b");
ignored!(ig20, ROOT, "a/**/b", "a/x/y/b");
ignored!(ig21, ROOT, r"\!xy", "!xy");
ignored!(ig22, ROOT, r"\#foo", "#foo");
ignored!(ig23, ROOT, "foo", "./foo");
ignored!(ig24, ROOT, "target", "grep/target");
ignored!(ig25, ROOT, "Cargo.lock", "./tabwriter-bin/Cargo.lock");
ignored!(ig26, ROOT, "/foo/bar/baz", "./foo/bar/baz");
ignored!(ig27, ROOT, "foo/", "xyz/foo", true);
ignored!(ig28, "./src", "/llvm/", "./src/llvm", true);
ignored!(ig29, ROOT, "node_modules/ ", "node_modules", true);
ignored!(ig30, ROOT, "**/", "foo/bar", true);
ignored!(ig31, ROOT, "path1/*", "path1/foo");
ignored!(ig32, ROOT, ".a/b", ".a/b");
ignored!(ig33, "./", ".a/b", ".a/b");
ignored!(ig34, ".", ".a/b", ".a/b");
ignored!(ig35, "./.", ".a/b", ".a/b");
ignored!(ig36, "././", ".a/b", ".a/b");
ignored!(ig37, "././.", ".a/b", ".a/b");
ignored!(ig38, ROOT, "\\[", "[");
ignored!(ig39, ROOT, "\\?", "?");
ignored!(ig40, ROOT, "\\*", "*");
ignored!(ig41, ROOT, "\\a", "a");
ignored!(ig42, ROOT, "s*.rs", "sfoo.rs");
ignored!(ig43, ROOT, "**", "foo.rs");
ignored!(ig44, ROOT, "**/**/*", "a/foo.rs");
not_ignored!(ignot1, ROOT, "amonths", "months");
not_ignored!(ignot2, ROOT, "monthsa", "months");
not_ignored!(ignot3, ROOT, "/src/*.rs", "src/grep/src/main.rs");
not_ignored!(ignot4, ROOT, "/*.c", "mozilla-sha1/sha1.c");
not_ignored!(ignot5, ROOT, "/src/*.rs", "src/grep/src/main.rs");
not_ignored!(ignot6, ROOT, "*.rs\n!src/main.rs", "src/main.rs");
not_ignored!(ignot7, ROOT, "foo/", "foo", false);
not_ignored!(ignot8, ROOT, "**/foo/**", "wat/src/afoo/bar/baz");
not_ignored!(ignot9, ROOT, "**/foo/**", "wat/src/fooa/bar/baz");
not_ignored!(ignot10, ROOT, "**/foo/bar", "foo/src/bar");
not_ignored!(ignot11, ROOT, "#foo", "#foo");
not_ignored!(ignot12, ROOT, "\n\n\n", "foo");
not_ignored!(ignot13, ROOT, "foo/**", "foo", true);
not_ignored!(
ignot14,
"./third_party/protobuf",
"m4/ltoptions.m4",
"./third_party/protobuf/csharp/src/packages/repositories.config"
);
not_ignored!(ignot15, ROOT, "!/bar", "foo/bar");
not_ignored!(ignot16, ROOT, "*\n!**/", "foo", true);
not_ignored!(ignot17, ROOT, "src/*.rs", "src/grep/src/main.rs");
not_ignored!(ignot18, ROOT, "path1/*", "path2/path1/foo");
not_ignored!(ignot19, ROOT, "s*.rs", "src/foo.rs");
fn bytes(s: &str) -> Vec<u8> {
s.to_string().into_bytes()
}
fn path_string<P: AsRef<Path>>(path: P) -> String {
path.as_ref().to_str().unwrap().to_string()
}
#[test]
fn parse_excludes_file1() {
let data = bytes("[core]\nexcludesFile = /foo/bar");
let got = super::parse_excludes_file(&data).unwrap();
assert_eq!(path_string(got), "/foo/bar");
}
#[test]
fn parse_excludes_file2() {
let data = bytes("[core]\nexcludesFile = ~/foo/bar");
let got = super::parse_excludes_file(&data).unwrap();
assert_eq!(path_string(got), super::expand_tilde("~/foo/bar"));
}
#[test]
fn parse_excludes_file3() {
let data = bytes("[core]\nexcludeFile = /foo/bar");
assert!(super::parse_excludes_file(&data).is_none());
}
#[test]
fn parse_excludes_file4() {
let data = bytes("[core]\nexcludesFile = \"~/foo/bar\"");
let got = super::parse_excludes_file(&data);
assert_eq!(
path_string(got.unwrap()),
super::expand_tilde("~/foo/bar")
);
}
#[test]
fn parse_excludes_file5() {
let data = bytes("[core]\nexcludesFile = \" \"~/foo/bar \" \"");
assert!(super::parse_excludes_file(&data).is_none());
}
// See: https://github.com/BurntSushi/ripgrep/issues/106
#[test]
fn regression_106() {
gi_from_str("/", " ");
}
#[test]
fn case_insensitive() {
let gi = GitignoreBuilder::new(ROOT)
.case_insensitive(true)
.unwrap()
.add_str(None, "*.html")
.unwrap()
.build()
.unwrap();
assert!(gi.matched("foo.html", false).is_ignore());
assert!(gi.matched("foo.HTML", false).is_ignore());
assert!(!gi.matched("foo.htm", false).is_ignore());
assert!(!gi.matched("foo.HTM", false).is_ignore());
}
ignored!(cs1, ROOT, "*.html", "foo.html");
not_ignored!(cs2, ROOT, "*.html", "foo.HTML");
not_ignored!(cs3, ROOT, "*.html", "foo.htm");
not_ignored!(cs4, ROOT, "*.html", "foo.HTM");
}

File diff suppressed because it is too large Load diff

549
crates/ignore/src/lib.rs Normal file
View file

@ -0,0 +1,549 @@
/*!
The ignore crate provides a fast recursive directory iterator that respects
various filters such as globs, file types and `.gitignore` files. The precise
matching rules and precedence is explained in the documentation for
`WalkBuilder`.
Secondarily, this crate exposes gitignore and file type matchers for use cases
that demand more fine-grained control.
# Example
This example shows the most basic usage of this crate. This code will
recursively traverse the current directory while automatically filtering out
files and directories according to ignore globs found in files like
`.ignore` and `.gitignore`:
```rust,no_run
use ignore::Walk;
for result in Walk::new("./") {
// Each item yielded by the iterator is either a directory entry or an
// error, so either print the path or the error.
match result {
Ok(entry) => println!("{}", entry.path().display()),
Err(err) => println!("ERROR: {}", err),
}
}
```
# Example: advanced
By default, the recursive directory iterator will ignore hidden files and
directories. This can be disabled by building the iterator with `WalkBuilder`:
```rust,no_run
use ignore::WalkBuilder;
for result in WalkBuilder::new("./").hidden(false).build() {
println!("{:?}", result);
}
```
See the documentation for `WalkBuilder` for many other options.
*/
#![deny(missing_docs)]
use std::path::{Path, PathBuf};
pub use crate::incremental::{IncrementalIgnore, IncrementalMatch};
pub use crate::walk::{
DirEntry, ParallelVisitor, ParallelVisitorBuilder, Walk, WalkBuilder,
WalkParallel, WalkState,
};
mod default_types;
mod dir;
pub mod gitignore;
mod incremental;
pub mod overrides;
mod pathutil;
pub mod types;
mod walk;
/// Represents an error that can occur when parsing a gitignore file.
#[derive(Debug)]
pub enum Error {
/// A collection of "soft" errors. These occur when adding an ignore
/// file partially succeeded.
Partial(Vec<Error>),
/// An error associated with a specific line number.
WithLineNumber {
/// The line number.
line: u64,
/// The underlying error.
err: Box<Error>,
},
/// An error associated with a particular file path.
WithPath {
/// The file path.
path: PathBuf,
/// The underlying error.
err: Box<Error>,
},
/// An error associated with a particular directory depth when recursively
/// walking a directory.
WithDepth {
/// The directory depth.
depth: usize,
/// The underlying error.
err: Box<Error>,
},
/// An error that occurs when a file loop is detected when traversing
/// symbolic links.
Loop {
/// The ancestor file path in the loop.
ancestor: PathBuf,
/// The child file path in the loop.
child: PathBuf,
},
/// An error that occurs when doing I/O, such as reading an ignore file.
Io(std::io::Error),
/// An error that occurs when trying to parse a glob.
Glob {
/// The original glob that caused this error. This glob, when
/// available, always corresponds to the glob provided by an end user.
/// e.g., It is the glob as written in a `.gitignore` file.
///
/// (This glob may be distinct from the glob that is actually
/// compiled, after accounting for `gitignore` semantics.)
glob: Option<String>,
/// The underlying glob error as a string.
err: String,
},
/// A type selection for a file type that is not defined.
UnrecognizedFileType(String),
/// A user specified file type definition could not be parsed.
InvalidDefinition,
}
impl Clone for Error {
fn clone(&self) -> Error {
match *self {
Error::Partial(ref errs) => Error::Partial(errs.clone()),
Error::WithLineNumber { line, ref err } => {
Error::WithLineNumber { line, err: err.clone() }
}
Error::WithPath { ref path, ref err } => {
Error::WithPath { path: path.clone(), err: err.clone() }
}
Error::WithDepth { depth, ref err } => {
Error::WithDepth { depth, err: err.clone() }
}
Error::Loop { ref ancestor, ref child } => Error::Loop {
ancestor: ancestor.clone(),
child: child.clone(),
},
Error::Io(ref err) => match err.raw_os_error() {
Some(e) => Error::Io(std::io::Error::from_raw_os_error(e)),
None => {
Error::Io(std::io::Error::new(err.kind(), err.to_string()))
}
},
Error::Glob { ref glob, ref err } => {
Error::Glob { glob: glob.clone(), err: err.clone() }
}
Error::UnrecognizedFileType(ref err) => {
Error::UnrecognizedFileType(err.clone())
}
Error::InvalidDefinition => Error::InvalidDefinition,
}
}
}
impl Error {
/// Returns true if this is a partial error.
///
/// A partial error occurs when only some operations failed while others
/// may have succeeded. For example, an ignore file may contain an invalid
/// glob among otherwise valid globs.
pub fn is_partial(&self) -> bool {
match *self {
Error::Partial(_) => true,
Error::WithLineNumber { ref err, .. } => err.is_partial(),
Error::WithPath { ref err, .. } => err.is_partial(),
Error::WithDepth { ref err, .. } => err.is_partial(),
_ => false,
}
}
/// Returns true if this error is exclusively an I/O error.
pub fn is_io(&self) -> bool {
match *self {
Error::Partial(ref errs) => errs.len() == 1 && errs[0].is_io(),
Error::WithLineNumber { ref err, .. } => err.is_io(),
Error::WithPath { ref err, .. } => err.is_io(),
Error::WithDepth { ref err, .. } => err.is_io(),
Error::Loop { .. } => false,
Error::Io(_) => true,
Error::Glob { .. } => false,
Error::UnrecognizedFileType(_) => false,
Error::InvalidDefinition => false,
}
}
/// Inspect the original [`std::io::Error`] if there is one.
///
/// [`None`] is returned if the [`Error`] doesn't correspond to an
/// [`std::io::Error`]. This might happen, for example, when the error was
/// produced because a cycle was found in the directory tree while
/// following symbolic links.
///
/// This method returns a borrowed value that is bound to the lifetime of the [`Error`]. To
/// obtain an owned value, the [`into_io_error`] can be used instead.
///
/// > This is the original [`std::io::Error`] and is _not_ the same as
/// > [`impl From<Error> for std::io::Error`][impl] which contains
/// > additional context about the error.
///
/// [`None`]: https://doc.rust-lang.org/stable/std/option/enum.Option.html#variant.None
/// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
/// [`From`]: https://doc.rust-lang.org/stable/std/convert/trait.From.html
/// [`Error`]: struct.Error.html
/// [`into_io_error`]: struct.Error.html#method.into_io_error
/// [impl]: struct.Error.html#impl-From%3CError%3E
pub fn io_error(&self) -> Option<&std::io::Error> {
match *self {
Error::Partial(ref errs) => {
if errs.len() == 1 {
errs[0].io_error()
} else {
None
}
}
Error::WithLineNumber { ref err, .. } => err.io_error(),
Error::WithPath { ref err, .. } => err.io_error(),
Error::WithDepth { ref err, .. } => err.io_error(),
Error::Loop { .. } => None,
Error::Io(ref err) => Some(err),
Error::Glob { .. } => None,
Error::UnrecognizedFileType(_) => None,
Error::InvalidDefinition => None,
}
}
/// Similar to [`io_error`] except consumes self to convert to the original
/// [`std::io::Error`] if one exists.
///
/// [`io_error`]: struct.Error.html#method.io_error
/// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
pub fn into_io_error(self) -> Option<std::io::Error> {
match self {
Error::Partial(mut errs) => {
if errs.len() == 1 {
errs.remove(0).into_io_error()
} else {
None
}
}
Error::WithLineNumber { err, .. } => err.into_io_error(),
Error::WithPath { err, .. } => err.into_io_error(),
Error::WithDepth { err, .. } => err.into_io_error(),
Error::Loop { .. } => None,
Error::Io(err) => Some(err),
Error::Glob { .. } => None,
Error::UnrecognizedFileType(_) => None,
Error::InvalidDefinition => None,
}
}
/// Returns a depth associated with recursively walking a directory (if
/// this error was generated from a recursive directory iterator).
pub fn depth(&self) -> Option<usize> {
match *self {
Error::WithPath { ref err, .. } => err.depth(),
Error::WithDepth { depth, .. } => Some(depth),
_ => None,
}
}
/// Turn an error into a tagged error with the given file path.
fn with_path<P: AsRef<Path>>(self, path: P) -> Error {
Error::WithPath {
path: path.as_ref().to_path_buf(),
err: Box::new(self),
}
}
/// Turn an error into a tagged error with the given depth.
fn with_depth(self, depth: usize) -> Error {
Error::WithDepth { depth, err: Box::new(self) }
}
/// Turn an error into a tagged error with the given file path and line
/// number. If path is empty, then it is omitted from the error.
fn tagged<P: AsRef<Path>>(self, path: P, lineno: u64) -> Error {
let errline =
Error::WithLineNumber { line: lineno, err: Box::new(self) };
if path.as_ref().as_os_str().is_empty() {
return errline;
}
errline.with_path(path)
}
/// Build an error from a walkdir error.
fn from_walkdir(err: walkdir::Error) -> Error {
let depth = err.depth();
if let (Some(anc), Some(child)) = (err.loop_ancestor(), err.path()) {
return Error::WithDepth {
depth,
err: Box::new(Error::Loop {
ancestor: anc.to_path_buf(),
child: child.to_path_buf(),
}),
};
}
let path = err.path().map(|p| p.to_path_buf());
let mut ig_err = Error::WithDepth {
depth,
err: Box::new(Error::Io(std::io::Error::from(err))),
};
if let Some(path) = path {
ig_err = Error::WithPath { path, err: Box::new(ig_err) };
}
ig_err
}
}
impl std::error::Error for Error {
#[allow(deprecated)]
fn description(&self) -> &str {
match *self {
Error::Partial(_) => "partial error",
Error::WithLineNumber { ref err, .. } => err.description(),
Error::WithPath { ref err, .. } => err.description(),
Error::WithDepth { ref err, .. } => err.description(),
Error::Loop { .. } => "file system loop found",
Error::Io(ref err) => err.description(),
Error::Glob { ref err, .. } => err,
Error::UnrecognizedFileType(_) => "unrecognized file type",
Error::InvalidDefinition => "invalid definition",
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Error::Partial(ref errs) => {
let msgs: Vec<String> =
errs.iter().map(|err| err.to_string()).collect();
write!(f, "{}", msgs.join("\n"))
}
Error::WithLineNumber { line, ref err } => {
write!(f, "line {}: {}", line, err)
}
Error::WithPath { ref path, ref err } => {
write!(f, "{}: {}", path.display(), err)
}
Error::WithDepth { ref err, .. } => err.fmt(f),
Error::Loop { ref ancestor, ref child } => write!(
f,
"File system loop found: \
{} points to an ancestor {}",
child.display(),
ancestor.display()
),
Error::Io(ref err) => err.fmt(f),
Error::Glob { glob: None, ref err } => write!(f, "{}", err),
Error::Glob { glob: Some(ref glob), ref err } => {
write!(f, "error parsing glob '{}': {}", glob, err)
}
Error::UnrecognizedFileType(ref ty) => {
write!(f, "unrecognized file type: {}", ty)
}
Error::InvalidDefinition => write!(
f,
"invalid definition (format is type:glob, e.g., \
html:*.html)"
),
}
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Error {
Error::Io(err)
}
}
#[derive(Debug, Default)]
struct PartialErrorBuilder(Vec<Error>);
impl PartialErrorBuilder {
fn push(&mut self, err: Error) {
self.0.push(err);
}
fn push_ignore_io(&mut self, err: Error) {
if !err.is_io() {
self.push(err);
}
}
fn maybe_push(&mut self, err: Option<Error>) {
if let Some(err) = err {
self.push(err);
}
}
fn maybe_push_ignore_io(&mut self, err: Option<Error>) {
if let Some(err) = err {
self.push_ignore_io(err);
}
}
fn into_error_option(mut self) -> Option<Error> {
if self.0.is_empty() {
None
} else if self.0.len() == 1 {
Some(self.0.pop().unwrap())
} else {
Some(Error::Partial(self.0))
}
}
}
/// The result of a glob match.
///
/// The type parameter `T` typically refers to a type that provides more
/// information about a particular match. For example, it might identify
/// the specific gitignore file and the specific glob pattern that caused
/// the match.
#[derive(Clone, Debug)]
pub enum Match<T> {
/// The path didn't match any glob.
None,
/// The highest precedent glob matched indicates the path should be
/// ignored.
Ignore(T),
/// The highest precedent glob matched indicates the path should be
/// whitelisted.
Whitelist(T),
}
impl<T> Match<T> {
/// Returns true if the match result didn't match any globs.
pub fn is_none(&self) -> bool {
match *self {
Match::None => true,
Match::Ignore(_) | Match::Whitelist(_) => false,
}
}
/// Returns true if the match result implies the path should be ignored.
pub fn is_ignore(&self) -> bool {
match *self {
Match::Ignore(_) => true,
Match::None | Match::Whitelist(_) => false,
}
}
/// Returns true if the match result implies the path should be
/// whitelisted.
pub fn is_whitelist(&self) -> bool {
match *self {
Match::Whitelist(_) => true,
Match::None | Match::Ignore(_) => false,
}
}
/// Inverts the match so that `Ignore` becomes `Whitelist` and
/// `Whitelist` becomes `Ignore`. A non-match remains the same.
pub fn invert(self) -> Match<T> {
match self {
Match::None => Match::None,
Match::Ignore(t) => Match::Whitelist(t),
Match::Whitelist(t) => Match::Ignore(t),
}
}
/// Return the value inside this match if it exists.
pub fn inner(&self) -> Option<&T> {
match *self {
Match::None => None,
Match::Ignore(ref t) => Some(t),
Match::Whitelist(ref t) => Some(t),
}
}
/// Apply the given function to the value inside this match.
///
/// If the match has no value, then return the match unchanged.
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Match<U> {
match self {
Match::None => Match::None,
Match::Ignore(t) => Match::Ignore(f(t)),
Match::Whitelist(t) => Match::Whitelist(f(t)),
}
}
/// Return the match if it is not none. Otherwise, return other.
pub fn or(self, other: Self) -> Self {
if self.is_none() { other } else { self }
}
}
#[cfg(test)]
mod tests {
use std::{
env, fs,
path::{Path, PathBuf},
};
/// A convenient result type alias.
pub(crate) type Result<T> =
std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
macro_rules! err {
($($tt:tt)*) => {
Box::<dyn std::error::Error + Send + Sync>::from(format!($($tt)*))
}
}
/// A simple wrapper for creating a temporary directory that is
/// automatically deleted when it's dropped.
///
/// We use this in lieu of tempfile because tempfile brings in too many
/// dependencies.
#[derive(Debug)]
pub struct TempDir(PathBuf);
impl Drop for TempDir {
fn drop(&mut self) {
fs::remove_dir_all(&self.0).unwrap();
}
}
impl TempDir {
/// Create a new empty temporary directory under the system's configured
/// temporary directory.
pub fn new() -> Result<TempDir> {
use std::sync::atomic::{AtomicUsize, Ordering};
static TRIES: usize = 100;
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let tmpdir = env::temp_dir();
for _ in 0..TRIES {
let count = COUNTER.fetch_add(1, Ordering::Relaxed);
let path = tmpdir.join("rust-ignore").join(count.to_string());
if path.is_dir() {
continue;
}
fs::create_dir_all(&path).map_err(|e| {
err!("failed to create {}: {}", path.display(), e)
})?;
return Ok(TempDir(path));
}
Err(err!("failed to create temp dir after {} tries", TRIES))
}
/// Return the underlying path to this temporary directory.
pub fn path(&self) -> &Path {
&self.0
}
}
}

View file

@ -0,0 +1,293 @@
/*!
The overrides module provides a way to specify a set of override globs.
This provides functionality similar to `--include` or `--exclude` in command
line tools.
*/
use std::path::Path;
use crate::{
Error, Match,
gitignore::{self, Gitignore, GitignoreBuilder},
};
/// Glob represents a single glob in an override matcher.
///
/// This is used to report information about the highest precedent glob
/// that matched.
///
/// Note that not all matches necessarily correspond to a specific glob. For
/// example, if there are one or more whitelist globs and a file path doesn't
/// match any glob in the set, then the file path is considered to be ignored.
///
/// The lifetime `'a` refers to the lifetime of the matcher that produced
/// this glob.
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub struct Glob<'a>(GlobInner<'a>);
#[derive(Clone, Debug)]
#[allow(dead_code)]
enum GlobInner<'a> {
/// No glob matched, but the file path should still be ignored.
UnmatchedIgnore,
/// A glob matched.
Matched(&'a gitignore::Glob),
}
impl<'a> Glob<'a> {
fn unmatched() -> Glob<'a> {
Glob(GlobInner::UnmatchedIgnore)
}
}
/// Manages a set of overrides provided explicitly by the end user.
#[derive(Clone, Debug)]
pub struct Override(Gitignore);
impl Override {
/// Returns an empty matcher that never matches any file path.
pub fn empty() -> Override {
Override(Gitignore::empty())
}
/// Returns the directory of this override set.
///
/// All matches are done relative to this path.
pub fn path(&self) -> &Path {
self.0.path()
}
/// Returns true if and only if this matcher is empty.
///
/// When a matcher is empty, it will never match any file path.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Returns the total number of ignore globs.
pub fn num_ignores(&self) -> u64 {
self.0.num_whitelists()
}
/// Returns the total number of whitelisted globs.
pub fn num_whitelists(&self) -> u64 {
self.0.num_ignores()
}
/// Returns whether the given file path matched a pattern in this override
/// matcher.
///
/// `is_dir` should be true if the path refers to a directory and false
/// otherwise.
///
/// If there are no overrides, then this always returns `Match::None`.
///
/// If there is at least one whitelist override and `is_dir` is false, then
/// this never returns `Match::None`, since non-matches are interpreted as
/// ignored.
///
/// The given path is matched to the globs relative to the path given
/// when building the override matcher. Specifically, before matching
/// `path`, its prefix (as determined by a common suffix of the directory
/// given) is stripped. If there is no common suffix/prefix overlap, then
/// `path` is assumed to reside in the same directory as the root path for
/// this set of overrides.
pub fn matched<'a, P: AsRef<Path>>(
&'a self,
path: P,
is_dir: bool,
) -> Match<Glob<'a>> {
if self.is_empty() {
return Match::None;
}
let mat = self.0.matched(path, is_dir).invert();
if mat.is_none() && self.num_whitelists() > 0 && !is_dir {
return Match::Ignore(Glob::unmatched());
}
mat.map(move |giglob| Glob(GlobInner::Matched(giglob)))
}
}
/// Builds a matcher for a set of glob overrides.
#[derive(Clone, Debug)]
pub struct OverrideBuilder {
builder: GitignoreBuilder,
}
impl OverrideBuilder {
/// Create a new override builder.
///
/// Matching is done relative to the directory path provided.
pub fn new<P: AsRef<Path>>(path: P) -> OverrideBuilder {
let mut builder = GitignoreBuilder::new(path);
builder.allow_unclosed_class(false);
OverrideBuilder { builder }
}
/// Builds a new override matcher from the globs added so far.
///
/// Once a matcher is built, no new globs can be added to it.
pub fn build(&self) -> Result<Override, Error> {
Ok(Override(self.builder.build()?))
}
/// Add a glob to the set of overrides.
///
/// Globs provided here have precisely the same semantics as a single
/// line in a `gitignore` file, where the meaning of `!` is inverted:
/// namely, `!` at the beginning of a glob will ignore a file. Without `!`,
/// all matches of the glob provided are treated as whitelist matches.
pub fn add(&mut self, glob: &str) -> Result<&mut OverrideBuilder, Error> {
self.builder.add_line(None, glob)?;
Ok(self)
}
/// Toggle whether the globs should be matched case insensitively or not.
///
/// When this option is changed, only globs added after the change will be
/// affected.
///
/// This is disabled by default.
pub fn case_insensitive(
&mut self,
yes: bool,
) -> Result<&mut OverrideBuilder, Error> {
// TODO: This should not return a `Result`. Fix this in the next semver
// release.
self.builder.case_insensitive(yes)?;
Ok(self)
}
/// Toggle whether unclosed character classes are allowed. When allowed,
/// a `[` without a matching `]` is treated literally instead of resulting
/// in a parse error.
///
/// For example, if this is set then the glob `[abc` will be treated as the
/// literal string `[abc` instead of returning an error.
///
/// By default, this is false. Generally speaking, enabling this leads to
/// worse failure modes since the glob parser becomes more permissive. You
/// might want to enable this when compatibility (e.g., with POSIX glob
/// implementations) is more important than good error messages.
///
/// This default is different from the default for [`Gitignore`]. Namely,
/// [`Gitignore`] is intended to match git's behavior as-is. But this
/// abstraction for "override" globs does not necessarily conform to any
/// other known specification and instead prioritizes better error
/// messages.
pub fn allow_unclosed_class(&mut self, yes: bool) -> &mut OverrideBuilder {
self.builder.allow_unclosed_class(yes);
self
}
}
#[cfg(test)]
mod tests {
use super::{Override, OverrideBuilder};
const ROOT: &'static str = "/home/andrew/foo";
fn ov(globs: &[&str]) -> Override {
let mut builder = OverrideBuilder::new(ROOT);
for glob in globs {
builder.add(glob).unwrap();
}
builder.build().unwrap()
}
#[test]
fn empty() {
let ov = ov(&[]);
assert!(ov.matched("a.foo", false).is_none());
assert!(ov.matched("a", false).is_none());
assert!(ov.matched("", false).is_none());
}
#[test]
fn simple() {
let ov = ov(&["*.foo", "!*.bar"]);
assert!(ov.matched("a.foo", false).is_whitelist());
assert!(ov.matched("a.foo", true).is_whitelist());
assert!(ov.matched("a.rs", false).is_ignore());
assert!(ov.matched("a.rs", true).is_none());
assert!(ov.matched("a.bar", false).is_ignore());
assert!(ov.matched("a.bar", true).is_ignore());
}
#[test]
fn only_ignores() {
let ov = ov(&["!*.bar"]);
assert!(ov.matched("a.rs", false).is_none());
assert!(ov.matched("a.rs", true).is_none());
assert!(ov.matched("a.bar", false).is_ignore());
assert!(ov.matched("a.bar", true).is_ignore());
}
#[test]
fn precedence() {
let ov = ov(&["*.foo", "!*.bar.foo"]);
assert!(ov.matched("a.foo", false).is_whitelist());
assert!(ov.matched("a.baz", false).is_ignore());
assert!(ov.matched("a.bar.foo", false).is_ignore());
}
#[test]
fn gitignore() {
let ov = ov(&["/foo", "bar/*.rs", "baz/**"]);
assert!(ov.matched("bar/lib.rs", false).is_whitelist());
assert!(ov.matched("bar/wat/lib.rs", false).is_ignore());
assert!(ov.matched("wat/bar/lib.rs", false).is_ignore());
assert!(ov.matched("foo", false).is_whitelist());
assert!(ov.matched("wat/foo", false).is_ignore());
assert!(ov.matched("baz", false).is_ignore());
assert!(ov.matched("baz/a", false).is_whitelist());
assert!(ov.matched("baz/a/b", false).is_whitelist());
}
#[test]
fn allow_directories() {
// This tests that directories are NOT ignored when they are unmatched.
let ov = ov(&["*.rs"]);
assert!(ov.matched("foo.rs", false).is_whitelist());
assert!(ov.matched("foo.c", false).is_ignore());
assert!(ov.matched("foo", false).is_ignore());
assert!(ov.matched("foo", true).is_none());
assert!(ov.matched("src/foo.rs", false).is_whitelist());
assert!(ov.matched("src/foo.c", false).is_ignore());
assert!(ov.matched("src/foo", false).is_ignore());
assert!(ov.matched("src/foo", true).is_none());
}
#[test]
fn absolute_path() {
let ov = ov(&["!/bar"]);
assert!(ov.matched("./foo/bar", false).is_none());
}
#[test]
fn case_insensitive() {
let ov = OverrideBuilder::new(ROOT)
.case_insensitive(true)
.unwrap()
.add("*.html")
.unwrap()
.build()
.unwrap();
assert!(ov.matched("foo.html", false).is_whitelist());
assert!(ov.matched("foo.HTML", false).is_whitelist());
assert!(ov.matched("foo.htm", false).is_ignore());
assert!(ov.matched("foo.HTM", false).is_ignore());
}
#[test]
fn default_case_sensitive() {
let ov =
OverrideBuilder::new(ROOT).add("*.html").unwrap().build().unwrap();
assert!(ov.matched("foo.html", false).is_whitelist());
assert!(ov.matched("foo.HTML", false).is_ignore());
assert!(ov.matched("foo.htm", false).is_ignore());
assert!(ov.matched("foo.HTM", false).is_ignore());
}
}

View file

@ -0,0 +1,171 @@
use std::{ffi::OsStr, path::Path};
use crate::walk::DirEntry;
/// Returns true if and only if this path is considered to be hidden.
///
/// # Platform behavior
///
/// ## Windows
///
/// This returns true if one of the following is true:
///
/// * The base name of the path starts with a `.`.
/// * The file attributes have the `HIDDEN` property set.
///
/// ## All other platforms
///
/// This only returns true if the base name of the path starts with a `.`.
pub(crate) fn is_hidden_path(dent: &Path) -> bool {
#[cfg(not(windows))]
fn imp(path: &Path) -> bool {
is_hidden_path_only(path)
}
#[cfg(windows)]
fn imp(path: &Path) -> bool {
use std::os::windows::fs::MetadataExt;
use winapi_util::file;
if let Ok(md) = path.metadata() {
if file::is_hidden(md.file_attributes() as u64) {
return true;
}
}
is_hidden_path_only(path)
}
imp(dent)
}
/// Returns true if and only if this directory entry is considered to be
/// hidden.
///
/// # Platform behavior
///
/// ## Windows
///
/// This returns true if one of the following is true:
///
/// * The base name of the path starts with a `.`.
/// * The file attributes have the `HIDDEN` property set.
///
/// ## All other platforms
///
/// This only returns true if the base name of the path starts with a `.`.
pub(crate) fn is_hidden_entry(dent: &DirEntry) -> bool {
#[cfg(not(windows))]
fn imp(dent: &DirEntry) -> bool {
is_hidden_path_only(dent.path())
}
#[cfg(windows)]
fn imp(dent: &DirEntry) -> bool {
use std::os::windows::fs::MetadataExt;
use winapi_util::file;
// This looks like we're doing an extra stat call, but on Windows, the
// directory traverser reuses the metadata retrieved from each directory
// entry and stores it on the DirEntry itself. So this is "free."
if let Ok(md) = dent.metadata() {
if file::is_hidden(md.file_attributes() as u64) {
return true;
}
}
is_hidden_path_only(dent.path())
}
imp(dent)
}
/// Returns true if and only if this path is considered to be hidden from only
/// the path itself.
///
/// This has the same behavior on all platforms.
fn is_hidden_path_only(path: &Path) -> bool {
if let Some(name) = file_name(path) {
name.as_encoded_bytes().starts_with(b".")
} else {
false
}
}
/// Strip `prefix` from the `path` and return the remainder.
///
/// If `path` doesn't have a prefix `prefix`, then return `None`.
pub(crate) fn strip_prefix<'a, P: AsRef<Path> + ?Sized>(
prefix: &'a P,
path: &'a Path,
) -> Option<&'a Path> {
#[cfg(unix)]
fn imp<'a>(prefix: &'a Path, path: &'a Path) -> Option<&'a Path> {
use std::os::unix::ffi::OsStrExt;
let prefix = prefix.as_os_str().as_bytes();
let path = path.as_os_str().as_bytes();
if prefix.len() > path.len() || prefix != &path[0..prefix.len()] {
None
} else {
Some(&Path::new(OsStr::from_bytes(&path[prefix.len()..])))
}
}
#[cfg(not(unix))]
fn imp<'a>(prefix: &'a Path, path: &'a Path) -> Option<&'a Path> {
path.strip_prefix(prefix).ok()
}
imp(prefix.as_ref(), path)
}
/// Returns true if this file path is just a file name. i.e., Its parent is
/// the empty string.
pub(crate) fn is_file_name<P: AsRef<Path>>(path: P) -> bool {
#[cfg(unix)]
{
memchr::memchr(b'/', path.as_ref().as_os_str().as_encoded_bytes())
.is_none()
}
#[cfg(not(unix))]
{
path.as_ref()
.parent()
.map(|p| p.as_os_str().is_empty())
.unwrap_or(false)
}
}
/// The final component of the path, if it is a normal file.
///
/// If the path terminates in `.`, `..`, or consists solely of a root of
/// prefix, this will return `None`.
pub(crate) fn file_name<'a, P: AsRef<Path> + ?Sized>(
path: &'a P,
) -> Option<&'a OsStr> {
#[cfg(unix)]
fn imp(path: &Path) -> Option<&OsStr> {
use std::os::unix::ffi::OsStrExt;
use memchr::memrchr;
let path = path.as_os_str().as_bytes();
if path.is_empty() {
return None;
} else if path.len() == 1 && path[0] == b'.' {
return None;
} else if path.last() == Some(&b'.') {
return None;
} else if path.len() >= 2 && &path[path.len() - 2..] == &b".."[..] {
return None;
}
let last_slash = memrchr(b'/', path).map(|i| i + 1).unwrap_or(0);
Some(OsStr::from_bytes(&path[last_slash..]))
}
#[cfg(not(unix))]
fn imp(path: &Path) -> Option<&OsStr> {
path.file_name()
}
imp(path.as_ref())
}

588
crates/ignore/src/types.rs Normal file
View file

@ -0,0 +1,588 @@
/*!
The types module provides a way of associating globs on file names to file
types.
This can be used to match specific types of files. For example, among
the default file types provided, the Rust file type is defined to be `*.rs`
with name `rust`. Similarly, the C file type is defined to be `*.{c,h}` with
name `c`.
Note that the set of default types may change over time.
# Example
This shows how to create and use a simple file type matcher using the default
file types defined in this crate.
```
use ignore::types::TypesBuilder;
let mut builder = TypesBuilder::new();
builder.add_defaults();
builder.select("rust");
let matcher = builder.build().unwrap();
assert!(matcher.matched("foo.rs", false).is_whitelist());
assert!(matcher.matched("foo.c", false).is_ignore());
```
# Example: negation
This is like the previous example, but shows how negating a file type works.
That is, this will let us match file paths that *don't* correspond to a
particular file type.
```
use ignore::types::TypesBuilder;
let mut builder = TypesBuilder::new();
builder.add_defaults();
builder.negate("c");
let matcher = builder.build().unwrap();
assert!(matcher.matched("foo.rs", false).is_none());
assert!(matcher.matched("foo.c", false).is_ignore());
```
# Example: custom file type definitions
This shows how to extend this library default file type definitions with
your own.
```
use ignore::types::TypesBuilder;
let mut builder = TypesBuilder::new();
builder.add_defaults();
builder.add("foo", "*.foo");
// Another way of adding a file type definition.
// This is useful when accepting input from an end user.
builder.add_def("bar:*.bar");
// Note: we only select `foo`, not `bar`.
builder.select("foo");
let matcher = builder.build().unwrap();
assert!(matcher.matched("x.foo", false).is_whitelist());
// This is ignored because we only selected the `foo` file type.
assert!(matcher.matched("x.bar", false).is_ignore());
```
We can also add file type definitions based on other definitions.
```
use ignore::types::TypesBuilder;
let mut builder = TypesBuilder::new();
builder.add_defaults();
builder.add("foo", "*.foo");
builder.add_def("bar:include:foo,cpp");
builder.select("bar");
let matcher = builder.build().unwrap();
assert!(matcher.matched("x.foo", false).is_whitelist());
assert!(matcher.matched("y.cpp", false).is_whitelist());
```
*/
use std::{collections::HashMap, path::Path, sync::Arc};
use {
globset::{GlobBuilder, GlobSet, GlobSetBuilder},
regex_automata::util::pool::Pool,
};
use crate::{Error, Match, default_types::DEFAULT_TYPES, pathutil::file_name};
/// Glob represents a single glob in a set of file type definitions.
///
/// There may be more than one glob for a particular file type.
///
/// This is used to report information about the highest precedent glob
/// that matched.
///
/// Note that not all matches necessarily correspond to a specific glob.
/// For example, if there are one or more selections and a file path doesn't
/// match any of those selections, then the file path is considered to be
/// ignored.
///
/// The lifetime `'a` refers to the lifetime of the underlying file type
/// definition, which corresponds to the lifetime of the file type matcher.
#[derive(Clone, Debug)]
pub struct Glob<'a>(GlobInner<'a>);
#[derive(Clone, Debug)]
enum GlobInner<'a> {
/// No glob matched, but the file path should still be ignored.
UnmatchedIgnore,
/// A glob matched.
Matched {
/// The file type definition which provided the glob.
def: &'a FileTypeDef,
},
}
impl<'a> Glob<'a> {
fn unmatched() -> Glob<'a> {
Glob(GlobInner::UnmatchedIgnore)
}
/// Return the file type definition that matched, if one exists. A file type
/// definition always exists when a specific definition matches a file
/// path.
pub fn file_type_def(&self) -> Option<&FileTypeDef> {
match self {
Glob(GlobInner::UnmatchedIgnore) => None,
Glob(GlobInner::Matched { def, .. }) => Some(def),
}
}
}
/// A single file type definition.
///
/// File type definitions can be retrieved in aggregate from a file type
/// matcher. File type definitions are also reported when its responsible
/// for a match.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FileTypeDef {
name: String,
globs: Vec<String>,
}
impl FileTypeDef {
/// Return the name of this file type.
pub fn name(&self) -> &str {
&self.name
}
/// Return the globs used to recognize this file type.
pub fn globs(&self) -> &[String] {
&self.globs
}
}
/// Types is a file type matcher.
#[derive(Clone, Debug)]
pub struct Types {
/// All of the file type definitions, sorted lexicographically by name.
defs: Vec<FileTypeDef>,
/// All of the selections made by the user.
selections: Vec<Selection<FileTypeDef>>,
/// Whether there is at least one Selection::Select in our selections.
/// When this is true, a Match::None is converted to Match::Ignore.
has_selected: bool,
/// A mapping from glob index in the set to two indices. The first is an
/// index into `selections` and the second is an index into the
/// corresponding file type definition's list of globs.
glob_to_selection: Vec<(usize, usize)>,
/// The set of all glob selections, used for actual matching.
set: GlobSet,
/// Temporary storage for globs that match.
matches: Arc<Pool<Vec<usize>>>,
}
/// Indicates the type of a selection for a particular file type.
#[derive(Clone, Debug)]
enum Selection<T> {
Select(String, T),
Negate(String, T),
}
impl<T> Selection<T> {
fn is_negated(&self) -> bool {
match *self {
Selection::Select(..) => false,
Selection::Negate(..) => true,
}
}
fn name(&self) -> &str {
match *self {
Selection::Select(ref name, _) => name,
Selection::Negate(ref name, _) => name,
}
}
fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Selection<U> {
match self {
Selection::Select(name, inner) => {
Selection::Select(name, f(inner))
}
Selection::Negate(name, inner) => {
Selection::Negate(name, f(inner))
}
}
}
fn inner(&self) -> &T {
match *self {
Selection::Select(_, ref inner) => inner,
Selection::Negate(_, ref inner) => inner,
}
}
}
impl Types {
/// Creates a new file type matcher that never matches any path and
/// contains no file type definitions.
pub fn empty() -> Types {
Types {
defs: vec![],
selections: vec![],
has_selected: false,
glob_to_selection: vec![],
set: GlobSetBuilder::new().build().unwrap(),
matches: Arc::new(Pool::with_available_parallelism_capacity(
|| vec![],
)),
}
}
/// Returns true if and only if this matcher has zero selections.
pub fn is_empty(&self) -> bool {
self.selections.is_empty()
}
/// Returns the number of selections used in this matcher.
pub fn len(&self) -> usize {
self.selections.len()
}
/// Return the set of current file type definitions.
///
/// Definitions and globs are sorted.
pub fn definitions(&self) -> &[FileTypeDef] {
&self.defs
}
/// Returns a match for the given path against this file type matcher.
///
/// The path is considered whitelisted if it matches a selected file type.
/// The path is considered ignored if it matches a negated file type.
/// If at least one file type is selected and `path` doesn't match, then
/// the path is also considered ignored.
pub fn matched<'a, P: AsRef<Path>>(
&'a self,
path: P,
is_dir: bool,
) -> Match<Glob<'a>> {
// File types don't apply to directories, and we can't do anything
// if our glob set is empty.
if is_dir || self.set.is_empty() {
return Match::None;
}
// We only want to match against the file name, so extract it.
// If one doesn't exist, then we can't match it.
let name = match file_name(path.as_ref()) {
Some(name) => name,
None if self.has_selected => {
return Match::Ignore(Glob::unmatched());
}
None => {
return Match::None;
}
};
let mut matches = self.matches.get();
self.set.matches_into(name, &mut *matches);
// The highest precedent match is the last one.
if let Some(&i) = matches.last() {
let (isel, _) = self.glob_to_selection[i];
let sel = &self.selections[isel];
let glob = Glob(GlobInner::Matched { def: sel.inner() });
return if sel.is_negated() {
Match::Ignore(glob)
} else {
Match::Whitelist(glob)
};
}
if self.has_selected {
Match::Ignore(Glob::unmatched())
} else {
Match::None
}
}
}
/// TypesBuilder builds a type matcher from a set of file type definitions and
/// a set of file type selections.
pub struct TypesBuilder {
types: HashMap<String, FileTypeDef>,
selections: Vec<Selection<()>>,
}
impl TypesBuilder {
/// Create a new builder for a file type matcher.
///
/// The builder contains *no* type definitions to start with. A set
/// of default type definitions can be added with `add_defaults`, and
/// additional type definitions can be added with `select` and `negate`.
pub fn new() -> TypesBuilder {
TypesBuilder { types: HashMap::new(), selections: vec![] }
}
/// Build the current set of file type definitions *and* selections into
/// a file type matcher.
pub fn build(&self) -> Result<Types, Error> {
let defs = self.definitions();
let has_selected = self.selections.iter().any(|s| !s.is_negated());
let mut selections = vec![];
let mut glob_to_selection = vec![];
let mut build_set = GlobSetBuilder::new();
for (isel, selection) in self.selections.iter().enumerate() {
let def = match self.types.get(selection.name()) {
Some(def) => def.clone(),
None => {
let name = selection.name().to_string();
return Err(Error::UnrecognizedFileType(name));
}
};
for (iglob, glob) in def.globs.iter().enumerate() {
build_set.add(
GlobBuilder::new(glob)
.literal_separator(true)
.build()
.map_err(|err| Error::Glob {
glob: Some(glob.to_string()),
err: err.kind().to_string(),
})?,
);
glob_to_selection.push((isel, iglob));
}
selections.push(selection.clone().map(move |_| def));
}
let set = build_set
.build()
.map_err(|err| Error::Glob { glob: None, err: err.to_string() })?;
Ok(Types {
defs,
selections,
has_selected,
glob_to_selection,
set,
matches: Arc::new(Pool::with_available_parallelism_capacity(
|| vec![],
)),
})
}
/// Return the set of current file type definitions.
///
/// Definitions and globs are sorted.
pub fn definitions(&self) -> Vec<FileTypeDef> {
let mut defs = vec![];
for def in self.types.values() {
let mut def = def.clone();
def.globs.sort();
defs.push(def);
}
defs.sort_by(|def1, def2| def1.name().cmp(def2.name()));
defs
}
/// Select the file type given by `name`.
///
/// If `name` is `all`, then all file types currently defined are selected.
pub fn select(&mut self, name: &str) -> &mut TypesBuilder {
if name == "all" {
for name in self.types.keys() {
self.selections.push(Selection::Select(name.to_string(), ()));
}
} else {
self.selections.push(Selection::Select(name.to_string(), ()));
}
self
}
/// Ignore the file type given by `name`.
///
/// If `name` is `all`, then all file types currently defined are negated.
pub fn negate(&mut self, name: &str) -> &mut TypesBuilder {
if name == "all" {
for name in self.types.keys() {
self.selections.push(Selection::Negate(name.to_string(), ()));
}
} else {
self.selections.push(Selection::Negate(name.to_string(), ()));
}
self
}
/// Clear any file type definitions for the type name given.
pub fn clear(&mut self, name: &str) -> &mut TypesBuilder {
self.types.remove(name);
self
}
/// Add a new file type definition. `name` can be arbitrary and `pat`
/// should be a glob recognizing file paths belonging to the `name` type.
///
/// If `name` is `all` or otherwise contains any character that is not a
/// Unicode letter or number, then an error is returned.
pub fn add(&mut self, name: &str, glob: &str) -> Result<(), Error> {
if name == "all" || !name.chars().all(|c| c.is_alphanumeric()) {
return Err(Error::InvalidDefinition);
}
let (key, glob) = (name.to_string(), glob.to_string());
self.types
.entry(key)
.or_insert_with(|| FileTypeDef {
name: name.to_string(),
globs: vec![],
})
.globs
.push(glob);
Ok(())
}
/// Add a new file type definition specified in string form. There are two
/// valid formats:
/// 1. `{name}:{glob}`. This defines a 'root' definition that associates the
/// given name with the given glob.
/// 2. `{name}:include:{comma-separated list of already defined names}.
/// This defines an 'include' definition that associates the given name
/// with the definitions of the given existing types.
/// Names may not include any characters that are not
/// Unicode letters or numbers.
pub fn add_def(&mut self, def: &str) -> Result<(), Error> {
let parts: Vec<&str> = def.split(':').collect();
match parts.len() {
2 => {
let name = parts[0];
let glob = parts[1];
if name.is_empty() || glob.is_empty() {
return Err(Error::InvalidDefinition);
}
self.add(name, glob)
}
3 => {
let name = parts[0];
let types_string = parts[2];
if name.is_empty()
|| parts[1] != "include"
|| types_string.is_empty()
{
return Err(Error::InvalidDefinition);
}
let types = types_string.split(',');
// Check ahead of time to ensure that all types specified are
// present and fail fast if not.
if types.clone().any(|t| !self.types.contains_key(t)) {
return Err(Error::InvalidDefinition);
}
for type_name in types {
let globs =
self.types.get(type_name).unwrap().globs.clone();
for glob in globs {
self.add(name, &glob)?;
}
}
Ok(())
}
_ => Err(Error::InvalidDefinition),
}
}
/// Add a set of default file type definitions.
pub fn add_defaults(&mut self) -> &mut TypesBuilder {
static MSG: &'static str = "adding a default type should never fail";
for &(names, exts) in DEFAULT_TYPES {
for name in names {
for ext in exts {
self.add(name, ext).expect(MSG);
}
}
}
self
}
}
#[cfg(test)]
mod tests {
use super::TypesBuilder;
macro_rules! matched {
($name:ident, $types:expr, $sel:expr, $selnot:expr,
$path:expr) => {
matched!($name, $types, $sel, $selnot, $path, true);
};
(not, $name:ident, $types:expr, $sel:expr, $selnot:expr,
$path:expr) => {
matched!($name, $types, $sel, $selnot, $path, false);
};
($name:ident, $types:expr, $sel:expr, $selnot:expr,
$path:expr, $matched:expr) => {
#[test]
fn $name() {
let mut btypes = TypesBuilder::new();
for tydef in $types {
btypes.add_def(tydef).unwrap();
}
for sel in $sel {
btypes.select(sel);
}
for selnot in $selnot {
btypes.negate(selnot);
}
let types = btypes.build().unwrap();
let mat = types.matched($path, false);
assert_eq!($matched, !mat.is_ignore());
}
};
}
fn types() -> Vec<&'static str> {
vec![
"html:*.html",
"html:*.htm",
"rust:*.rs",
"js:*.js",
"py:*.py",
"python:*.py",
"foo:*.{rs,foo}",
"combo:include:html,rust",
]
}
matched!(match1, types(), vec!["rust"], vec![], "lib.rs");
matched!(match2, types(), vec!["html"], vec![], "index.html");
matched!(match3, types(), vec!["html"], vec![], "index.htm");
matched!(match4, types(), vec!["html", "rust"], vec![], "main.rs");
matched!(match5, types(), vec![], vec![], "index.html");
matched!(match6, types(), vec![], vec!["rust"], "index.html");
matched!(match7, types(), vec!["foo"], vec!["rust"], "main.foo");
matched!(match8, types(), vec!["combo"], vec![], "index.html");
matched!(match9, types(), vec!["combo"], vec![], "lib.rs");
matched!(match10, types(), vec!["py"], vec![], "main.py");
matched!(match11, types(), vec!["python"], vec![], "main.py");
matched!(not, matchnot1, types(), vec!["rust"], vec![], "index.html");
matched!(not, matchnot2, types(), vec![], vec!["rust"], "main.rs");
matched!(not, matchnot3, types(), vec!["foo"], vec!["rust"], "main.rs");
matched!(not, matchnot4, types(), vec!["rust"], vec!["foo"], "main.rs");
matched!(not, matchnot5, types(), vec!["rust"], vec!["foo"], "main.foo");
matched!(not, matchnot6, types(), vec!["combo"], vec![], "leftpad.js");
matched!(not, matchnot7, types(), vec!["py"], vec![], "index.html");
matched!(not, matchnot8, types(), vec!["python"], vec![], "doc.md");
#[test]
fn test_invalid_defs() {
let mut btypes = TypesBuilder::new();
for tydef in types() {
btypes.add_def(tydef).unwrap();
}
// Preserve the original definitions for later comparison.
let original_defs = btypes.definitions();
let bad_defs = vec![
// Reference to type that does not exist
"combo:include:html,qwerty",
// Bad format
"combo:foobar:html,rust",
"",
];
for def in bad_defs {
assert!(btypes.add_def(def).is_err());
// Ensure that nothing changed, even if some of the includes were valid.
assert_eq!(btypes.definitions(), original_defs);
}
}
}

2757
crates/ignore/src/walk.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,216 @@
# Based on https://github.com/behnam/gitignore-test/blob/master/.gitignore
### file in root
# MATCH /file_root_1
file_root_00
# NO_MATCH
file_root_01/
# NO_MATCH
file_root_02/*
# NO_MATCH
file_root_03/**
# MATCH /file_root_10
/file_root_10
# NO_MATCH
/file_root_11/
# NO_MATCH
/file_root_12/*
# NO_MATCH
/file_root_13/**
# NO_MATCH
*/file_root_20
# NO_MATCH
*/file_root_21/
# NO_MATCH
*/file_root_22/*
# NO_MATCH
*/file_root_23/**
# MATCH /file_root_30
**/file_root_30
# NO_MATCH
**/file_root_31/
# NO_MATCH
**/file_root_32/*
# NO_MATCH
**/file_root_33/**
### file in sub-dir
# MATCH /parent_dir/file_deep_1
file_deep_00
# NO_MATCH
file_deep_01/
# NO_MATCH
file_deep_02/*
# NO_MATCH
file_deep_03/**
# NO_MATCH
/file_deep_10
# NO_MATCH
/file_deep_11/
# NO_MATCH
/file_deep_12/*
# NO_MATCH
/file_deep_13/**
# MATCH /parent_dir/file_deep_20
*/file_deep_20
# NO_MATCH
*/file_deep_21/
# NO_MATCH
*/file_deep_22/*
# NO_MATCH
*/file_deep_23/**
# MATCH /parent_dir/file_deep_30
**/file_deep_30
# NO_MATCH
**/file_deep_31/
# NO_MATCH
**/file_deep_32/*
# NO_MATCH
**/file_deep_33/**
### dir in root
# MATCH /dir_root_00
dir_root_00
# MATCH /dir_root_01
dir_root_01/
# MATCH /dir_root_02
dir_root_02/*
# MATCH /dir_root_03
dir_root_03/**
# MATCH /dir_root_10
/dir_root_10
# MATCH /dir_root_11
/dir_root_11/
# MATCH /dir_root_12
/dir_root_12/*
# MATCH /dir_root_13
/dir_root_13/**
# NO_MATCH
*/dir_root_20
# NO_MATCH
*/dir_root_21/
# NO_MATCH
*/dir_root_22/*
# NO_MATCH
*/dir_root_23/**
# MATCH /dir_root_30
**/dir_root_30
# MATCH /dir_root_31
**/dir_root_31/
# MATCH /dir_root_32
**/dir_root_32/*
# MATCH /dir_root_33
**/dir_root_33/**
### dir in sub-dir
# MATCH /parent_dir/dir_deep_00
dir_deep_00
# MATCH /parent_dir/dir_deep_01
dir_deep_01/
# NO_MATCH
dir_deep_02/*
# NO_MATCH
dir_deep_03/**
# NO_MATCH
/dir_deep_10
# NO_MATCH
/dir_deep_11/
# NO_MATCH
/dir_deep_12/*
# NO_MATCH
/dir_deep_13/**
# MATCH /parent_dir/dir_deep_20
*/dir_deep_20
# MATCH /parent_dir/dir_deep_21
*/dir_deep_21/
# MATCH /parent_dir/dir_deep_22
*/dir_deep_22/*
# MATCH /parent_dir/dir_deep_23
*/dir_deep_23/**
# MATCH /parent_dir/dir_deep_30
**/dir_deep_30
# MATCH /parent_dir/dir_deep_31
**/dir_deep_31/
# MATCH /parent_dir/dir_deep_32
**/dir_deep_32/*
# MATCH /parent_dir/dir_deep_33
**/dir_deep_33/**

View file

@ -0,0 +1,318 @@
use std::path::Path;
use ignore::gitignore::{Gitignore, GitignoreBuilder};
const IGNORE_FILE: &'static str =
"tests/gitignore_matched_path_or_any_parents_tests.gitignore";
fn get_gitignore() -> Gitignore {
let mut builder = GitignoreBuilder::new("ROOT");
let error = builder.add(IGNORE_FILE);
assert!(error.is_none(), "failed to open gitignore file");
builder.build().unwrap()
}
#[test]
#[should_panic(expected = "path is expected to be under the root")]
fn test_path_should_be_under_root() {
let gitignore = get_gitignore();
let path = "/tmp/some_file";
gitignore.matched_path_or_any_parents(Path::new(path), false);
assert!(false);
}
#[test]
fn test_files_in_root() {
let gitignore = get_gitignore();
let m = |path: &str| {
gitignore.matched_path_or_any_parents(Path::new(path), false)
};
// 0x
assert!(m("ROOT/file_root_00").is_ignore());
assert!(m("ROOT/file_root_01").is_none());
assert!(m("ROOT/file_root_02").is_none());
assert!(m("ROOT/file_root_03").is_none());
// 1x
assert!(m("ROOT/file_root_10").is_ignore());
assert!(m("ROOT/file_root_11").is_none());
assert!(m("ROOT/file_root_12").is_none());
assert!(m("ROOT/file_root_13").is_none());
// 2x
assert!(m("ROOT/file_root_20").is_none());
assert!(m("ROOT/file_root_21").is_none());
assert!(m("ROOT/file_root_22").is_none());
assert!(m("ROOT/file_root_23").is_none());
// 3x
assert!(m("ROOT/file_root_30").is_ignore());
assert!(m("ROOT/file_root_31").is_none());
assert!(m("ROOT/file_root_32").is_none());
assert!(m("ROOT/file_root_33").is_none());
}
#[test]
fn test_files_in_deep() {
let gitignore = get_gitignore();
let m = |path: &str| {
gitignore.matched_path_or_any_parents(Path::new(path), false)
};
// 0x
assert!(m("ROOT/parent_dir/file_deep_00").is_ignore());
assert!(m("ROOT/parent_dir/file_deep_01").is_none());
assert!(m("ROOT/parent_dir/file_deep_02").is_none());
assert!(m("ROOT/parent_dir/file_deep_03").is_none());
// 1x
assert!(m("ROOT/parent_dir/file_deep_10").is_none());
assert!(m("ROOT/parent_dir/file_deep_11").is_none());
assert!(m("ROOT/parent_dir/file_deep_12").is_none());
assert!(m("ROOT/parent_dir/file_deep_13").is_none());
// 2x
assert!(m("ROOT/parent_dir/file_deep_20").is_ignore());
assert!(m("ROOT/parent_dir/file_deep_21").is_none());
assert!(m("ROOT/parent_dir/file_deep_22").is_none());
assert!(m("ROOT/parent_dir/file_deep_23").is_none());
// 3x
assert!(m("ROOT/parent_dir/file_deep_30").is_ignore());
assert!(m("ROOT/parent_dir/file_deep_31").is_none());
assert!(m("ROOT/parent_dir/file_deep_32").is_none());
assert!(m("ROOT/parent_dir/file_deep_33").is_none());
}
#[test]
fn test_dirs_in_root() {
let gitignore = get_gitignore();
let m = |path: &str, is_dir: bool| {
gitignore.matched_path_or_any_parents(Path::new(path), is_dir)
};
// 00
assert!(m("ROOT/dir_root_00", true).is_ignore());
assert!(m("ROOT/dir_root_00/file", false).is_ignore());
assert!(m("ROOT/dir_root_00/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_00/child_dir/file", false).is_ignore());
// 01
assert!(m("ROOT/dir_root_01", true).is_ignore());
assert!(m("ROOT/dir_root_01/file", false).is_ignore());
assert!(m("ROOT/dir_root_01/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_01/child_dir/file", false).is_ignore());
// 02
assert!(m("ROOT/dir_root_02", true).is_none()); // dir itself doesn't match
assert!(m("ROOT/dir_root_02/file", false).is_ignore());
assert!(m("ROOT/dir_root_02/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_02/child_dir/file", false).is_ignore());
// 03
assert!(m("ROOT/dir_root_03", true).is_none()); // dir itself doesn't match
assert!(m("ROOT/dir_root_03/file", false).is_ignore());
assert!(m("ROOT/dir_root_03/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_03/child_dir/file", false).is_ignore());
// 10
assert!(m("ROOT/dir_root_10", true).is_ignore());
assert!(m("ROOT/dir_root_10/file", false).is_ignore());
assert!(m("ROOT/dir_root_10/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_10/child_dir/file", false).is_ignore());
// 11
assert!(m("ROOT/dir_root_11", true).is_ignore());
assert!(m("ROOT/dir_root_11/file", false).is_ignore());
assert!(m("ROOT/dir_root_11/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_11/child_dir/file", false).is_ignore());
// 12
assert!(m("ROOT/dir_root_12", true).is_none()); // dir itself doesn't match
assert!(m("ROOT/dir_root_12/file", false).is_ignore());
assert!(m("ROOT/dir_root_12/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_12/child_dir/file", false).is_ignore());
// 13
assert!(m("ROOT/dir_root_13", true).is_none());
assert!(m("ROOT/dir_root_13/file", false).is_ignore());
assert!(m("ROOT/dir_root_13/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_13/child_dir/file", false).is_ignore());
// 20
assert!(m("ROOT/dir_root_20", true).is_none());
assert!(m("ROOT/dir_root_20/file", false).is_none());
assert!(m("ROOT/dir_root_20/child_dir", true).is_none());
assert!(m("ROOT/dir_root_20/child_dir/file", false).is_none());
// 21
assert!(m("ROOT/dir_root_21", true).is_none());
assert!(m("ROOT/dir_root_21/file", false).is_none());
assert!(m("ROOT/dir_root_21/child_dir", true).is_none());
assert!(m("ROOT/dir_root_21/child_dir/file", false).is_none());
// 22
assert!(m("ROOT/dir_root_22", true).is_none());
assert!(m("ROOT/dir_root_22/file", false).is_none());
assert!(m("ROOT/dir_root_22/child_dir", true).is_none());
assert!(m("ROOT/dir_root_22/child_dir/file", false).is_none());
// 23
assert!(m("ROOT/dir_root_23", true).is_none());
assert!(m("ROOT/dir_root_23/file", false).is_none());
assert!(m("ROOT/dir_root_23/child_dir", true).is_none());
assert!(m("ROOT/dir_root_23/child_dir/file", false).is_none());
// 30
assert!(m("ROOT/dir_root_30", true).is_ignore());
assert!(m("ROOT/dir_root_30/file", false).is_ignore());
assert!(m("ROOT/dir_root_30/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_30/child_dir/file", false).is_ignore());
// 31
assert!(m("ROOT/dir_root_31", true).is_ignore());
assert!(m("ROOT/dir_root_31/file", false).is_ignore());
assert!(m("ROOT/dir_root_31/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_31/child_dir/file", false).is_ignore());
// 32
assert!(m("ROOT/dir_root_32", true).is_none()); // dir itself doesn't match
assert!(m("ROOT/dir_root_32/file", false).is_ignore());
assert!(m("ROOT/dir_root_32/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_32/child_dir/file", false).is_ignore());
// 33
assert!(m("ROOT/dir_root_33", true).is_none()); // dir itself doesn't match
assert!(m("ROOT/dir_root_33/file", false).is_ignore());
assert!(m("ROOT/dir_root_33/child_dir", true).is_ignore());
assert!(m("ROOT/dir_root_33/child_dir/file", false).is_ignore());
}
#[test]
fn test_dirs_in_deep() {
let gitignore = get_gitignore();
let m = |path: &str, is_dir: bool| {
gitignore.matched_path_or_any_parents(Path::new(path), is_dir)
};
// 00
assert!(m("ROOT/parent_dir/dir_deep_00", true).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_00/file", false).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_00/child_dir", true).is_ignore());
assert!(
m("ROOT/parent_dir/dir_deep_00/child_dir/file", false).is_ignore()
);
// 01
assert!(m("ROOT/parent_dir/dir_deep_01", true).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_01/file", false).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_01/child_dir", true).is_ignore());
assert!(
m("ROOT/parent_dir/dir_deep_01/child_dir/file", false).is_ignore()
);
// 02
assert!(m("ROOT/parent_dir/dir_deep_02", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_02/file", false).is_none());
assert!(m("ROOT/parent_dir/dir_deep_02/child_dir", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_02/child_dir/file", false).is_none());
// 03
assert!(m("ROOT/parent_dir/dir_deep_03", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_03/file", false).is_none());
assert!(m("ROOT/parent_dir/dir_deep_03/child_dir", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_03/child_dir/file", false).is_none());
// 10
assert!(m("ROOT/parent_dir/dir_deep_10", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_10/file", false).is_none());
assert!(m("ROOT/parent_dir/dir_deep_10/child_dir", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_10/child_dir/file", false).is_none());
// 11
assert!(m("ROOT/parent_dir/dir_deep_11", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_11/file", false).is_none());
assert!(m("ROOT/parent_dir/dir_deep_11/child_dir", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_11/child_dir/file", false).is_none());
// 12
assert!(m("ROOT/parent_dir/dir_deep_12", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_12/file", false).is_none());
assert!(m("ROOT/parent_dir/dir_deep_12/child_dir", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_12/child_dir/file", false).is_none());
// 13
assert!(m("ROOT/parent_dir/dir_deep_13", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_13/file", false).is_none());
assert!(m("ROOT/parent_dir/dir_deep_13/child_dir", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_13/child_dir/file", false).is_none());
// 20
assert!(m("ROOT/parent_dir/dir_deep_20", true).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_20/file", false).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_20/child_dir", true).is_ignore());
assert!(
m("ROOT/parent_dir/dir_deep_20/child_dir/file", false).is_ignore()
);
// 21
assert!(m("ROOT/parent_dir/dir_deep_21", true).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_21/file", false).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_21/child_dir", true).is_ignore());
assert!(
m("ROOT/parent_dir/dir_deep_21/child_dir/file", false).is_ignore()
);
// 22
// dir itself doesn't match
assert!(m("ROOT/parent_dir/dir_deep_22", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_22/file", false).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_22/child_dir", true).is_ignore());
assert!(
m("ROOT/parent_dir/dir_deep_22/child_dir/file", false).is_ignore()
);
// 23
// dir itself doesn't match
assert!(m("ROOT/parent_dir/dir_deep_23", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_23/file", false).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_23/child_dir", true).is_ignore());
assert!(
m("ROOT/parent_dir/dir_deep_23/child_dir/file", false).is_ignore()
);
// 30
assert!(m("ROOT/parent_dir/dir_deep_30", true).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_30/file", false).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_30/child_dir", true).is_ignore());
assert!(
m("ROOT/parent_dir/dir_deep_30/child_dir/file", false).is_ignore()
);
// 31
assert!(m("ROOT/parent_dir/dir_deep_31", true).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_31/file", false).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_31/child_dir", true).is_ignore());
assert!(
m("ROOT/parent_dir/dir_deep_31/child_dir/file", false).is_ignore()
);
// 32
// dir itself doesn't match
assert!(m("ROOT/parent_dir/dir_deep_32", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_32/file", false).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_32/child_dir", true).is_ignore());
assert!(
m("ROOT/parent_dir/dir_deep_32/child_dir/file", false).is_ignore()
);
// 33
// dir itself doesn't match
assert!(m("ROOT/parent_dir/dir_deep_33", true).is_none());
assert!(m("ROOT/parent_dir/dir_deep_33/file", false).is_ignore());
assert!(m("ROOT/parent_dir/dir_deep_33/child_dir", true).is_ignore());
assert!(
m("ROOT/parent_dir/dir_deep_33/child_dir/file", false).is_ignore()
);
}

View file

@ -0,0 +1,2 @@
ignore/this/path
# This file begins with a BOM (U+FEFF)

View file

@ -0,0 +1,17 @@
use ignore::gitignore::GitignoreBuilder;
const IGNORE_FILE: &'static str = "tests/gitignore_skip_bom.gitignore";
/// Skip a Byte-Order Mark (BOM) at the beginning of the file, matching Git's
/// behavior.
///
/// Ref: <https://github.com/BurntSushi/ripgrep/issues/2177>
#[test]
fn gitignore_skip_bom() {
let mut builder = GitignoreBuilder::new("ROOT");
let error = builder.add(IGNORE_FILE);
assert!(error.is_none(), "failed to open gitignore file");
let g = builder.build().unwrap();
assert!(g.matched("ignore/this/path", false).is_ignore());
}

View file

@ -4,4 +4,11 @@ linker = "aarch64-linux-gnu-gcc"
linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-feature=-crt-static"]
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
linker = "arm-linux-gnueabihf-gcc"
# Statically link Visual Studio redistributables on Windows builds
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
[target.aarch64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]
[target.'cfg(target_env = "gnu")']
rustflags = ["-C", "link-args=-Wl,-z,nodelete"]

View file

@ -121,7 +121,7 @@ dist
.AppleDouble
.LSOverride
# Icon must end with two
# Icon must end with two
Icon
@ -194,8 +194,14 @@ Cargo.lock
!.yarn/sdks
!.yarn/versions
*.node
# Generated
*.node
*.wasm
index.d.ts
index.js
browser.js
tailwindcss-oxide.wasi-browser.js
tailwindcss-oxide.wasi.cjs
tailwindcss-oxide.wasi.d.cts
wasi-worker-browser.mjs
wasi-worker.mjs

View file

@ -8,10 +8,10 @@ crate-type = ["cdylib"]
[dependencies]
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
napi = { version = "2.16.11", default-features = false, features = ["napi4"] }
napi-derive = "2.16.12"
napi = { version = "3.11.0", default-features = false, features = ["napi4"] }
napi-derive = "3.6.0"
tailwindcss-oxide = { path = "../oxide" }
rayon = "1.5.3"
rayon = "1.12.0"
[build-dependencies]
napi-build = "2.0.1"
napi-build = "2.3.2"

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-android-arm-eabi",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,6 +22,6 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-android-arm64",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,6 +22,6 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-darwin-arm64",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,6 +22,6 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-darwin-x64",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,6 +22,6 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-freebsd-x64",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,6 +22,6 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-linux-arm-gnueabihf",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,6 +22,6 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-linux-arm64-gnu",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,7 +22,7 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
},
"libc": [
"glibc"

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-linux-arm64-musl",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,7 +22,7 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
},
"libc": [
"musl"

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-linux-x64-gnu",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,7 +22,7 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
},
"libc": [
"glibc"

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-linux-x64-musl",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,7 +22,7 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
},
"libc": [
"musl"

View file

@ -0,0 +1 @@
node_modules/

View file

@ -0,0 +1,3 @@
# `@tailwindcss/oxide-wasm32-wasi`
This is the **wasm32-wasip1-threads** build of `@tailwindcss/oxide`

View file

@ -0,0 +1,42 @@
{
"name": "@tailwindcss/oxide-wasm32-wasi",
"version": "4.3.3",
"main": "tailwindcss-oxide.wasi.cjs",
"files": [
"tailwindcss-oxide.wasm32-wasi.wasm",
"tailwindcss-oxide.wasi.cjs",
"tailwindcss-oxide.wasi-browser.js",
"wasi-worker.mjs",
"wasi-worker-browser.mjs"
],
"license": "MIT",
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
},
"publishConfig": {
"provenance": true,
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
"directory": "crates/node"
},
"browser": "tailwindcss-oxide.wasi-browser.js",
"dependencies": {
"@napi-rs/wasm-runtime": "^1.2.2",
"@emnapi/core": "^1.11.3",
"@emnapi/runtime": "^1.11.3",
"@tybys/wasm-util": "^0.10.3",
"@emnapi/wasi-threads": "^1.2.3",
"tslib": "^2.8.1"
},
"bundledDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
"@emnapi/runtime",
"@tybys/wasm-util",
"@emnapi/wasi-threads",
"tslib"
]
}

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-win32-arm64-msvc",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,6 +22,6 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide-win32-x64-msvc",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -22,6 +22,6 @@
},
"license": "MIT",
"engines": {
"node": ">= 10"
"node": ">= 20"
}
}

View file

@ -1,6 +1,6 @@
{
"name": "@tailwindcss/oxide",
"version": "4.0.0-beta.4",
"version": "4.3.3",
"repository": {
"type": "git",
"url": "git+https://github.com/tailwindlabs/tailwindcss.git",
@ -9,28 +9,38 @@
"main": "index.js",
"types": "index.d.ts",
"napi": {
"name": "tailwindcss-oxide",
"triples": {
"additional": [
"armv7-linux-androideabi",
"aarch64-linux-android",
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"armv7-unknown-linux-gnueabihf",
"x86_64-unknown-linux-musl",
"x86_64-unknown-freebsd",
"i686-pc-windows-msvc",
"aarch64-pc-windows-msvc"
]
"binaryName": "tailwindcss-oxide",
"packageName": "@tailwindcss/oxide",
"targets": [
"armv7-linux-androideabi",
"aarch64-linux-android",
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"armv7-unknown-linux-gnueabihf",
"x86_64-unknown-linux-musl",
"x86_64-unknown-freebsd",
"i686-pc-windows-msvc",
"aarch64-pc-windows-msvc",
"wasm32-wasip1-threads"
],
"wasm": {
"initialMemory": 16384,
"browser": {
"fs": true
}
}
},
"license": "MIT",
"devDependencies": {
"@napi-rs/cli": "^2.18.4"
"@emnapi/core": "1.11.3",
"@emnapi/runtime": "1.11.3",
"@napi-rs/cli": "3.7.4",
"@napi-rs/wasm-runtime": "^1.2.2",
"emnapi": "1.11.3"
},
"engines": {
"node": ">= 10"
"node": ">= 20"
},
"files": [
"index.js",
@ -41,10 +51,13 @@
"access": "public"
},
"scripts": {
"artifacts": "napi artifacts",
"build": "napi build --platform --release --no-const-enum",
"build": "pnpm run build:platform && pnpm run build:wasm",
"build:platform": "napi build --platform --release",
"postbuild:platform": "node ./scripts/move-artifacts.mjs",
"build:wasm": "napi build --release --target wasm32-wasip1-threads",
"postbuild:wasm": "node ./scripts/move-artifacts.mjs",
"dev": "cargo watch --quiet --shell 'npm run build'",
"build:debug": "napi build --platform --no-const-enum",
"build:debug": "napi build --platform",
"version": "napi version"
},
"optionalDependencies": {
@ -57,7 +70,8 @@
"@tailwindcss/oxide-linux-arm64-musl": "workspace:*",
"@tailwindcss/oxide-linux-x64-gnu": "workspace:*",
"@tailwindcss/oxide-linux-x64-musl": "workspace:*",
"@tailwindcss/oxide-win32-x64-msvc": "workspace:*",
"@tailwindcss/oxide-win32-arm64-msvc": "workspace:*"
"@tailwindcss/oxide-wasm32-wasi": "workspace:*",
"@tailwindcss/oxide-win32-arm64-msvc": "workspace:*",
"@tailwindcss/oxide-win32-x64-msvc": "workspace:*"
}
}

View file

@ -0,0 +1,37 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import url from 'node:url'
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
let root = path.resolve(__dirname, '..')
const tailwindcssOxideRoot = path.join(root)
// Move napi artifacts into sub packages
for (let file of await fs.readdir(tailwindcssOxideRoot)) {
if (file.startsWith('tailwindcss-oxide.') && file.endsWith('.node')) {
let target = file.split('.')[1]
await fs.cp(
path.join(tailwindcssOxideRoot, file),
path.join(tailwindcssOxideRoot, 'npm', target, file),
)
console.log(`Moved ${file} to npm/${target}`)
}
}
// Move napi wasm artifacts into sub package
let wasmArtifacts = {
'tailwindcss-oxide.debug.wasm': 'tailwindcss-oxide.wasm32-wasi.debug.wasm',
'tailwindcss-oxide.wasm': 'tailwindcss-oxide.wasm32-wasi.wasm',
'tailwindcss-oxide.wasi-browser.js': 'tailwindcss-oxide.wasi-browser.js',
'tailwindcss-oxide.wasi.cjs': 'tailwindcss-oxide.wasi.cjs',
'wasi-worker-browser.mjs': 'wasi-worker-browser.mjs',
'wasi-worker.mjs': 'wasi-worker.mjs',
}
for (let file of await fs.readdir(tailwindcssOxideRoot)) {
if (!wasmArtifacts[file]) continue
await fs.cp(
path.join(tailwindcssOxideRoot, file),
path.join(tailwindcssOxideRoot, 'npm', 'wasm32-wasi', wasmArtifacts[file]),
)
console.log(`Moved ${file} to npm/wasm32-wasi`)
}

View file

@ -28,12 +28,30 @@ pub struct GlobEntry {
pub pattern: String,
}
#[derive(Debug, Clone)]
#[napi(object)]
pub struct SourceEntry {
/// Base path of the glob
pub base: String,
/// Glob pattern
pub pattern: String,
/// Negated flag
pub negated: bool,
}
impl From<ChangedContent> for tailwindcss_oxide::ChangedContent {
fn from(changed_content: ChangedContent) -> Self {
Self {
file: changed_content.file.map(Into::into),
content: changed_content.content,
if let Some(file) = changed_content.file {
return tailwindcss_oxide::ChangedContent::File(file.into(), changed_content.extension);
}
if let Some(contents) = changed_content.content {
return tailwindcss_oxide::ChangedContent::Content(contents, changed_content.extension);
}
unreachable!()
}
}
@ -55,13 +73,23 @@ impl From<tailwindcss_oxide::GlobEntry> for GlobEntry {
}
}
impl From<SourceEntry> for tailwindcss_oxide::PublicSourceEntry {
fn from(source: SourceEntry) -> Self {
Self {
base: source.base,
pattern: source.pattern,
negated: source.negated,
}
}
}
// ---
#[derive(Debug, Clone)]
#[napi(object)]
pub struct ScannerOptions {
/// Glob sources
pub sources: Option<Vec<GlobEntry>>,
pub sources: Option<Vec<SourceEntry>>,
}
#[derive(Debug, Clone)]
@ -85,11 +113,10 @@ impl Scanner {
#[napi(constructor)]
pub fn new(opts: ScannerOptions) -> Self {
Self {
scanner: tailwindcss_oxide::Scanner::new(
opts
.sources
.map(|x| x.into_iter().map(Into::into).collect()),
),
scanner: tailwindcss_oxide::Scanner::new(match opts.sources {
Some(sources) => sources.into_iter().map(Into::into).collect(),
None => vec![],
}),
}
}
@ -138,6 +165,11 @@ impl Scanner {
self.scanner.get_files()
}
#[napi(getter)]
pub fn scanned_files(&self) -> Vec<String> {
self.scanner.get_scanned_files()
}
#[napi(getter)]
pub fn globs(&mut self) -> Vec<GlobEntry> {
self
@ -147,4 +179,14 @@ impl Scanner {
.map(Into::into)
.collect()
}
#[napi(getter)]
pub fn normalized_sources(&mut self) -> Vec<GlobEntry> {
self
.scanner
.get_normalized_sources()
.into_iter()
.map(Into::into)
.collect()
}
}

View file

@ -4,19 +4,23 @@ version = "0.1.0"
edition = "2021"
[dependencies]
bstr = "1.10.0"
bstr = "1.11.3"
globwalk = "0.9.1"
log = "0.4.22"
rayon = "1.10.0"
fxhash = { package = "rustc-hash", version = "2.0.0" }
crossbeam = "0.8.4"
fxhash = { package = "rustc-hash", version = "2.1.1" }
tracing = { version = "0.1.40", features = [] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
walkdir = "2.5.0"
ignore = "0.4.23"
dunce = "1.0.5"
bexpand = "1.2.0"
glob-match = "0.2.1"
fast-glob = "0.4.3"
classification-macros = { path = "../classification-macros" }
ignore = { path = "../ignore" }
regex = "1.11.1"
[dev-dependencies]
insta = "1.48.0"
tempfile = "3.13.0"
pretty_assertions = "1.4.1"
unicode-width = "0.2.0"

View file

@ -1,82 +1,80 @@
use std::{ascii::escape_default, fmt::Display};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct Cursor<'a> {
// The input we're scanning
pub input: &'a [u8],
// The location of the cursor in the input
pub pos: usize,
/// Is the cursor at the start of the input
pub at_start: bool,
/// Is the cursor at the end of the input
pub at_end: bool,
/// The previously consumed character
/// If `at_start` is true, this will be NUL
pub prev: u8,
/// The current character
pub curr: u8,
/// The upcoming character (if any)
/// If `at_end` is true, this will be NUL
pub next: u8,
}
impl<'a> Cursor<'a> {
#[inline(always)]
pub fn new(input: &'a [u8]) -> Self {
let mut cursor = Self {
input,
pos: 0,
at_start: true,
at_end: false,
prev: 0x00,
curr: 0x00,
next: 0x00,
};
cursor.move_to(0);
cursor
Self { input, pos: 0 }
}
pub fn rewind_by(&mut self, amount: usize) {
self.move_to(self.pos.saturating_sub(amount));
/// The current byte at `pos`, or 0x00 if past the end.
#[inline(always)]
pub fn curr(&self) -> u8 {
if self.pos < self.input.len() {
unsafe { *self.input.get_unchecked(self.pos) }
} else {
0x00
}
}
/// The next byte at `pos + 1`, or 0x00 if past the end.
#[inline(always)]
pub fn next(&self) -> u8 {
let next_pos = self.pos + 1;
if next_pos < self.input.len() {
unsafe { *self.input.get_unchecked(next_pos) }
} else {
0x00
}
}
/// The previous byte at `pos - 1`, or 0x00 if at the start.
#[inline(always)]
pub fn prev(&self) -> u8 {
if self.pos > 0 {
unsafe { *self.input.get_unchecked(self.pos - 1) }
} else {
0x00
}
}
pub fn advance_by(&mut self, amount: usize) {
self.move_to(self.pos.saturating_add(amount));
}
#[inline(always)]
pub fn advance(&mut self) {
self.pos += 1;
}
#[inline(always)]
pub fn advance_twice(&mut self) {
self.pos += 2;
}
pub fn move_to(&mut self, pos: usize) {
let len = self.input.len();
let pos = pos.clamp(0, len);
self.pos = pos;
self.at_start = pos == 0;
self.at_end = pos + 1 >= len;
self.prev = if pos > 0 { self.input[pos - 1] } else { 0x00 };
self.curr = if pos < len { self.input[pos] } else { 0x00 };
self.next = if pos + 1 < len {
self.input[pos + 1]
} else {
0x00
};
self.pos = pos.min(self.input.len());
}
}
impl<'a> Display for Cursor<'a> {
impl Display for Cursor<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let len = self.input.len().to_string();
let pos = format!("{: >len_count$}", self.pos, len_count = len.len());
write!(f, "{}/{} ", pos, len)?;
if self.at_start {
if self.pos == 0 {
write!(f, "S ")?;
} else if self.at_end {
} else if self.pos + 1 >= self.input.len() {
write!(f, "E ")?;
} else {
write!(f, "M ")?;
@ -93,9 +91,9 @@ impl<'a> Display for Cursor<'a> {
write!(
f,
"[{} {} {}]",
to_str(self.prev),
to_str(self.curr),
to_str(self.next)
to_str(self.prev()),
to_str(self.curr()),
to_str(self.next())
)
}
}
@ -103,57 +101,34 @@ impl<'a> Display for Cursor<'a> {
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_cursor() {
let mut cursor = Cursor::new(b"hello world");
assert_eq!(cursor.pos, 0);
assert!(cursor.at_start);
assert!(!cursor.at_end);
assert_eq!(cursor.prev, 0x00);
assert_eq!(cursor.curr, b'h');
assert_eq!(cursor.next, b'e');
assert_eq!(cursor.prev(), 0x00);
assert_eq!(cursor.curr(), b'h');
assert_eq!(cursor.next(), b'e');
cursor.advance_by(1);
assert_eq!(cursor.pos, 1);
assert!(!cursor.at_start);
assert!(!cursor.at_end);
assert_eq!(cursor.prev, b'h');
assert_eq!(cursor.curr, b'e');
assert_eq!(cursor.next, b'l');
assert_eq!(cursor.prev(), b'h');
assert_eq!(cursor.curr(), b'e');
assert_eq!(cursor.next(), b'l');
// Advancing too far should stop at the end
cursor.advance_by(10);
assert_eq!(cursor.pos, 11);
assert!(!cursor.at_start);
assert!(cursor.at_end);
assert_eq!(cursor.prev, b'd');
assert_eq!(cursor.curr, 0x00);
assert_eq!(cursor.next, 0x00);
assert_eq!(cursor.prev(), b'd');
assert_eq!(cursor.curr(), 0x00);
assert_eq!(cursor.next(), 0x00);
// Can't advance past the end
cursor.advance_by(1);
assert_eq!(cursor.pos, 11);
assert!(!cursor.at_start);
assert!(cursor.at_end);
assert_eq!(cursor.prev, b'd');
assert_eq!(cursor.curr, 0x00);
assert_eq!(cursor.next, 0x00);
cursor.rewind_by(1);
assert_eq!(cursor.pos, 10);
assert!(!cursor.at_start);
assert!(cursor.at_end);
assert_eq!(cursor.prev, b'l');
assert_eq!(cursor.curr, b'd');
assert_eq!(cursor.next, 0x00);
cursor.rewind_by(10);
assert_eq!(cursor.pos, 0);
assert!(cursor.at_start);
assert!(!cursor.at_end);
assert_eq!(cursor.prev, 0x00);
assert_eq!(cursor.curr, b'h');
assert_eq!(cursor.next, b'e');
assert_eq!(cursor.prev(), b'd');
assert_eq!(cursor.curr(), 0x00);
assert_eq!(cursor.next(), 0x00);
}
}

View file

@ -0,0 +1,457 @@
use crate::cursor;
use crate::extractor::bracket_stack::BracketStack;
use crate::extractor::machine::{Machine, MachineState};
use crate::extractor::string_machine::StringMachine;
use crate::extractor::CssVariableMachine;
use classification_macros::ClassifyBytes;
use std::marker::PhantomData;
#[derive(Debug, Default)]
pub struct IdleState;
/// Parsing the property, e.g.:
///
/// ```text
/// [color:red]
/// ^^^^^
///
/// [--my-color:red]
/// ^^^^^^^^^^
/// ```
#[derive(Debug, Default)]
pub struct ParsingPropertyState;
/// Parsing the value, e.g.:
///
/// ```text
/// [color:red]
/// ^^^
/// ```
#[derive(Debug, Default)]
pub struct ParsingValueState;
/// Extracts arbitrary properties from the input, including the brackets.
///
/// E.g.:
///
/// ```text
/// [color:red]
/// ^^^^^^^^^^^
///
/// [--my-color:red]
/// ^^^^^^^^^^^^^^^^
/// ```
#[derive(Debug, Default)]
pub struct ArbitraryPropertyMachine<State = IdleState> {
/// Start position of the arbitrary value
start_pos: usize,
/// Track brackets to ensure they are balanced
bracket_stack: BracketStack,
css_variable_machine: CssVariableMachine,
string_machine: StringMachine,
_state: PhantomData<State>,
}
impl<State> ArbitraryPropertyMachine<State> {
#[inline(always)]
fn transition<NextState>(&self) -> ArbitraryPropertyMachine<NextState> {
ArbitraryPropertyMachine {
start_pos: self.start_pos,
bracket_stack: Default::default(),
css_variable_machine: Default::default(),
string_machine: Default::default(),
_state: PhantomData,
}
}
}
impl Machine for ArbitraryPropertyMachine<IdleState> {
#[inline(always)]
fn reset(&mut self) {}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match cursor.curr().into() {
// Start of an arbitrary property
Class::OpenBracket => {
self.start_pos = cursor.pos;
cursor.advance();
self.transition::<ParsingPropertyState>().next(cursor)
}
// Anything else is not a valid start of an arbitrary value
_ => MachineState::Idle,
}
}
}
impl Machine for ArbitraryPropertyMachine<ParsingPropertyState> {
#[inline(always)]
fn reset(&mut self) {}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
let len = cursor.input.len();
while cursor.pos < len {
match cursor.curr().into() {
Class::Dash => match cursor.next().into() {
// Start of a CSS variable
//
// E.g.: `[--my-color:red]`
// ^^
Class::Dash => return self.parse_property_variable(cursor),
// Dashes are allowed in the property name
//
// E.g.: `[background-color:red]`
// ^
_ => cursor.advance(),
},
// Alpha characters are allowed in the property name
//
// E.g.: `[color:red]`
// ^^^^^
Class::AlphaLower => cursor.advance(),
// End of the property name, but there must be at least a single character
Class::Colon if cursor.pos > self.start_pos + 1 => {
cursor.advance();
return self.transition::<ParsingValueState>().next(cursor);
}
// Anything else is not a valid property character
_ => return self.restart(),
}
}
self.restart()
}
}
impl ArbitraryPropertyMachine<ParsingPropertyState> {
fn parse_property_variable(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match self.css_variable_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => match cursor.next().into() {
// End of the CSS variable, must be followed by a `:`
//
// E.g.: `[--my-color:red]`
// ^
Class::Colon => {
cursor.advance_twice();
self.transition::<ParsingValueState>().next(cursor)
}
// Invalid arbitrary property
_ => self.restart(),
},
}
}
}
impl Machine for ArbitraryPropertyMachine<ParsingValueState> {
#[inline(always)]
fn reset(&mut self) {
self.bracket_stack.reset();
}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
let len = cursor.input.len();
let start_of_value_pos = cursor.pos;
while cursor.pos < len {
match cursor.curr().into() {
Class::Escape => match cursor.next().into() {
// An escaped whitespace character is not allowed
//
// E.g.: `[color:var(--my-\ color)]`
// ^
Class::Whitespace => return self.restart(),
// An escaped character, skip the next character, resume after
//
// E.g.: `[color:var(--my-\#color)]`
// ^
_ => cursor.advance_twice(),
},
Class::OpenParen | Class::OpenBracket | Class::OpenCurly => {
if !self.bracket_stack.push(cursor.curr()) {
return self.restart();
}
cursor.advance();
}
Class::CloseParen | Class::CloseBracket | Class::CloseCurly
if !self.bracket_stack.is_empty() =>
{
if !self.bracket_stack.pop(cursor.curr()) {
return self.restart();
}
cursor.advance();
}
// End of an arbitrary value
//
// 1. All brackets must be balanced
// 2. There must be at least a single character inside the brackets
Class::CloseBracket
if self.start_pos + 1 != cursor.pos && self.bracket_stack.is_empty() =>
{
return self.done(self.start_pos, cursor)
}
// Start of a string
Class::Quote => return self.parse_string(cursor),
// Another `:` inside of an arbitrary property is only valid inside of a string or
// inside of brackets. Everywhere else, it's invalid.
//
// E.g.: `[color:red:blue]`
// ^ Not valid
// E.g.: `[background:url(https://example.com)]`
// ^ Valid
// E.g.: `[content:'a:b:c:']`
// ^ ^ ^ Valid
Class::Colon if self.bracket_stack.is_empty() => return self.restart(),
// Any kind of whitespace is not allowed
Class::Whitespace => return self.restart(),
// URLs are not allowed
Class::Slash if start_of_value_pos == cursor.pos => return self.restart(),
// String interpolation-like syntax is not allowed. E.g.: `[${x}]`
Class::Dollar if matches!(cursor.next().into(), Class::OpenCurly) => {
return self.restart()
}
// An `!` at the top-level is invalid. We don't allow things to end with
// `!important` either as we have dedicated syntax for this.
Class::Exclamation if self.bracket_stack.is_empty() => {
return self.restart();
}
// Everything else is valid
_ => cursor.advance(),
};
}
self.restart()
}
}
impl ArbitraryPropertyMachine<ParsingValueState> {
fn parse_string(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match self.string_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => {
cursor.advance();
self.next(cursor)
}
}
}
}
#[derive(Clone, Copy, ClassifyBytes)]
enum Class {
#[bytes(b'(')]
OpenParen,
#[bytes(b'[')]
OpenBracket,
#[bytes(b'{')]
OpenCurly,
#[bytes(b')')]
CloseParen,
#[bytes(b']')]
CloseBracket,
#[bytes(b'}')]
CloseCurly,
#[bytes(b'\\')]
Escape,
#[bytes(b'"', b'\'', b'`')]
Quote,
#[bytes(b'-')]
Dash,
#[bytes(b'$')]
Dollar,
#[bytes_range(b'a'..=b'z')]
AlphaLower,
#[bytes(b':')]
Colon,
#[bytes(b'/')]
Slash,
#[bytes(b'!')]
Exclamation,
#[bytes(b' ', b'\t', b'\n', b'\r', b'\x0C')]
Whitespace,
#[bytes(b'\0')]
End,
#[fallback]
Other,
}
#[cfg(test)]
mod tests {
use super::{ArbitraryPropertyMachine, IdleState};
use crate::extractor::machine::Machine;
use pretty_assertions::assert_eq;
#[test]
#[ignore]
fn test_arbitrary_property_machine_performance() {
let input = r#"<button class="[color:red] [background-color:red] [--my-color:red] [background:url('https://example.com')]">"#.repeat(10);
ArbitraryPropertyMachine::<IdleState>::test_throughput(1_000_000, &input);
ArbitraryPropertyMachine::<IdleState>::test_duration_once(&input);
todo!()
}
#[test]
fn test_arbitrary_property_machine_extraction() {
for (input, expected) in [
// Simple arbitrary property
("[color:red]", vec!["[color:red]"]),
// Name with dashes
("[background-color:red]", vec!["[background-color:red]"]),
// Name with leading `-` is valid
("[-webkit-value:red]", vec!["[-webkit-value:red]"]),
// Setting a CSS Variable
("[--my-color:red]", vec!["[--my-color:red]"]),
// Value with nested brackets
(
"[background:url(https://example.com)]",
vec!["[background:url(https://example.com)]"],
),
// Value containing strings
(
"[background:url('https://example.com')]",
vec!["[background:url('https://example.com')]"],
),
// --------------------------------------------------------
// Invalid CSS Variable
("[--my#color:red]", vec![]),
// Spaces are not allowed
("[color: red]", vec![]),
// Multiple colons are not allowed
("[color:red:blue]", vec![]),
// Only alphanumeric characters are allowed in the property name
("[background_color:red]", vec![]),
// A color is required
("[red]", vec![]),
// The property cannot be empty
("[:red]", vec![]),
// Empty brackets are not allowed
("[]", vec![]),
// URLs
("[http://example.com]", vec![]),
("[https://example.com]", vec![]),
// Missing colon in more complex example
(r#"[CssClass("gap-y-4")]"#, vec![]),
// Brackets must be balanced
("[background:url(https://example.com]", vec![]),
// Many brackets (>= 8) must be balanced
(
"[background:url(https://example.com?q={[{[([{[[2]]}])]}]})]",
vec!["[background:url(https://example.com?q={[{[([{[[2]]}])]}]})]"],
),
// A property containing `!` at the top-level is invalid
("[color:red!]", vec![]),
("[color:red!important]", vec![]),
] {
for wrapper in [
// No wrapper
"{}",
// With leading spaces
" {}",
// With trailing spaces
"{} ",
// Surrounded by spaces
" {} ",
// Inside a string
"'{}'",
// Inside a function call
"fn({})",
// Inside nested function calls
"fn1(fn2({}))",
// --------------------------
//
// HTML
// Inside a class (on its own)
r#"<div class="{}"></div>"#,
// Inside a class (first)
r#"<div class="{} foo"></div>"#,
// Inside a class (second)
r#"<div class="foo {}"></div>"#,
// Inside a class (surrounded)
r#"<div class="foo {} bar"></div>"#,
// --------------------------
//
// JavaScript
// Inside a variable
r#"let classes = '{}';"#,
// Inside an object (key)
r#"let classes = { '{}': true };"#,
// Inside an object (no spaces, key)
r#"let classes = {'{}':true};"#,
// Inside an object (value)
r#"let classes = { primary: '{}' };"#,
// Inside an object (no spaces, value)
r#"let classes = {primary:'{}'};"#,
] {
let input = wrapper.replace("{}", input);
let actual = ArbitraryPropertyMachine::<IdleState>::test_extract_all(&input);
if actual != expected {
dbg!(&input);
}
assert_eq!(actual, expected);
}
}
}
#[test]
fn test_exceptions() {
for (input, expected) in [
// JS string interpolation
// In key
("[${x}:value]", vec![]),
// As part of the key
("[background-${property}:value]", vec![]),
// In value
("[key:${x}]", vec![]),
// As part of the value
("[key:value-${x}]", vec![]),
// Allowed in strings
("[--img:url('${x}')]", vec!["[--img:url('${x}')]"]),
] {
assert_eq!(
ArbitraryPropertyMachine::<IdleState>::test_extract_all(input),
expected
);
}
}
}

View file

@ -0,0 +1,213 @@
use crate::cursor;
use crate::extractor::bracket_stack::BracketStack;
use crate::extractor::machine::{Machine, MachineState};
use crate::extractor::string_machine::StringMachine;
use classification_macros::ClassifyBytes;
/// Extracts arbitrary values including the brackets.
///
/// E.g.:
///
/// ```text
/// bg-[#0088cc]
/// ^^^^^^^^^
///
/// bg-red-500/[20%]
/// ^^^^^
/// ```
#[derive(Debug, Default)]
pub struct ArbitraryValueMachine {
/// Track brackets to ensure they are balanced
bracket_stack: BracketStack,
string_machine: StringMachine,
}
impl Machine for ArbitraryValueMachine {
#[inline(always)]
fn reset(&mut self) {
self.bracket_stack.reset();
}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
// An arbitrary value must start with an open bracket
if Class::OpenBracket != cursor.curr().into() {
return MachineState::Idle;
}
let start_pos = cursor.pos;
cursor.advance();
let len = cursor.input.len();
while cursor.pos < len {
match cursor.curr().into() {
Class::Escape => match cursor.next().into() {
// An escaped whitespace character is not allowed
//
// E.g.: `[color:var(--my-\ color)]`
// ^
Class::Whitespace => {
cursor.advance_twice();
return self.restart();
}
// An escaped character, skip the next character, resume after
//
// E.g.: `[color:var(--my-\#color)]`
// ^
_ => cursor.advance_twice(),
},
Class::OpenParen | Class::OpenBracket | Class::OpenCurly => {
if !self.bracket_stack.push(cursor.curr()) {
return self.restart();
}
cursor.advance();
}
Class::CloseParen | Class::CloseBracket | Class::CloseCurly
if !self.bracket_stack.is_empty() =>
{
if !self.bracket_stack.pop(cursor.curr()) {
return self.restart();
}
cursor.advance();
}
// End of an arbitrary value
//
// 1. All brackets must be balanced
// 2. There must be at least a single character inside the brackets
Class::CloseBracket
if start_pos + 1 != cursor.pos && self.bracket_stack.is_empty() =>
{
return self.done(start_pos, cursor);
}
// Start of a string
Class::Quote => match self.string_machine.next(cursor) {
MachineState::Idle => return self.restart(),
MachineState::Done(_) => cursor.advance(),
},
// Any kind of whitespace is not allowed
Class::Whitespace => return self.restart(),
// String interpolation-like syntax is not allowed. E.g.: `[${x}]`
Class::Dollar if matches!(cursor.next().into(), Class::OpenCurly) => {
return self.restart()
}
// Everything else is valid
_ => cursor.advance(),
};
}
self.restart()
}
}
#[derive(Clone, Copy, PartialEq, ClassifyBytes)]
enum Class {
#[bytes(b'\\')]
Escape,
#[bytes(b'(')]
OpenParen,
#[bytes(b')')]
CloseParen,
#[bytes(b'[')]
OpenBracket,
#[bytes(b']')]
CloseBracket,
#[bytes(b'{')]
OpenCurly,
#[bytes(b'}')]
CloseCurly,
#[bytes(b'"', b'\'', b'`')]
Quote,
#[bytes(b' ', b'\t', b'\n', b'\r', b'\x0C')]
Whitespace,
#[bytes(b'$')]
Dollar,
#[fallback]
Other,
}
#[cfg(test)]
mod tests {
use super::ArbitraryValueMachine;
use crate::extractor::machine::Machine;
use pretty_assertions::assert_eq;
#[test]
#[ignore]
fn test_arbitrary_value_machine_performance() {
let input = r#"<div class="[color:red] [[data-foo]] [url('https://tailwindcss.com')] [url(https://tailwindcss.com)]"></div>"#.repeat(100);
ArbitraryValueMachine::test_throughput(100_000, &input);
ArbitraryValueMachine::test_duration_once(&input);
todo!()
}
#[test]
fn test_arbitrary_value_machine_extraction() {
for (input, expected) in [
// Simple variable
("[#0088cc]", vec!["[#0088cc]"]),
// With parentheses
(
"[url(https://tailwindcss.com)]",
vec!["[url(https://tailwindcss.com)]"],
),
// With strings, where bracket balancing doesn't matter
("['[({])}']", vec!["['[({])}']"]),
// With strings later in the input
(
"[url('https://tailwindcss.com?[{]}')]",
vec!["[url('https://tailwindcss.com?[{]}')]"],
),
// With nested brackets
("[[data-foo]]", vec!["[[data-foo]]"]),
(
"[&>[data-slot=icon]:last-child]",
vec!["[&>[data-slot=icon]:last-child]"],
),
// With data types
("[length:32rem]", vec!["[length:32rem]"]),
// Spaces are not allowed
("[ #0088cc ]", vec![]),
// Unbalanced brackets are not allowed
("[foo[bar]", vec![]),
// Empty brackets are not allowed
("[]", vec![]),
] {
assert_eq!(ArbitraryValueMachine::test_extract_all(input), expected);
}
}
#[test]
fn test_exceptions() {
for (input, expected) in [
// JS string interpolation
("[${x}]", vec![]),
("[url(${x})]", vec![]),
// Allowed in strings
("[url('${x}')]", vec!["[url('${x}')]"]),
] {
assert_eq!(ArbitraryValueMachine::test_extract_all(input), expected);
}
}
}

View file

@ -0,0 +1,413 @@
use crate::cursor;
use crate::extractor::bracket_stack::BracketStack;
use crate::extractor::css_variable_machine::CssVariableMachine;
use crate::extractor::machine::{Machine, MachineState};
use crate::extractor::string_machine::StringMachine;
use classification_macros::ClassifyBytes;
use std::marker::PhantomData;
#[derive(Debug, Default)]
pub struct IdleState;
/// Currently parsing the inside of the arbitrary variable
///
/// ```text
/// (--my-opacity)
/// ^^^^^^^^^^^^
/// ```
#[derive(Debug, Default)]
pub struct ParsingState;
/// Currently parsing the data type of the arbitrary variable
///
/// ```text
/// (length:--my-opacity)
/// ^^^^^^^
/// ```
#[derive(Debug, Default)]
pub struct ParsingDataTypeState;
/// Currently parsing the fallback of the arbitrary variable
///
/// ```text
/// (--my-opacity,50%)
/// ^^^^
/// ```
#[derive(Debug, Default)]
pub struct ParsingFallbackState;
/// Extracts arbitrary variables including the parens.
///
/// E.g.:
///
/// ```text
/// (--my-value)
/// ^^^^^^^^^^^^
///
/// bg-red-500/(--my-opacity)
/// ^^^^^^^^^^^^^^
/// ```
#[derive(Debug, Default)]
pub struct ArbitraryVariableMachine<State = IdleState> {
/// Start position of the arbitrary variable
start_pos: usize,
/// Track brackets to ensure they are balanced
bracket_stack: BracketStack,
string_machine: StringMachine,
css_variable_machine: CssVariableMachine,
_state: PhantomData<State>,
}
impl<State> ArbitraryVariableMachine<State> {
#[inline(always)]
fn transition<NextState>(&self) -> ArbitraryVariableMachine<NextState> {
ArbitraryVariableMachine {
start_pos: self.start_pos,
bracket_stack: Default::default(),
string_machine: Default::default(),
css_variable_machine: Default::default(),
_state: PhantomData,
}
}
}
impl Machine for ArbitraryVariableMachine<IdleState> {
#[inline(always)]
fn reset(&mut self) {}
#[inline(always)]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match cursor.curr().into() {
// Arbitrary variables start with `(` followed by a CSS variable
//
// E.g.: `(--my-variable)`
// ^^
//
Class::OpenParen => match cursor.next().into() {
Class::Dash => {
self.start_pos = cursor.pos;
cursor.advance();
self.transition::<ParsingState>().next(cursor)
}
Class::AlphaLower => {
self.start_pos = cursor.pos;
cursor.advance();
self.transition::<ParsingDataTypeState>().next(cursor)
}
_ => MachineState::Idle,
},
// Everything else, is not a valid start of the arbitrary variable. But the next
// character might be a valid start for a new utility.
_ => MachineState::Idle,
}
}
}
impl Machine for ArbitraryVariableMachine<ParsingState> {
#[inline(always)]
fn reset(&mut self) {}
#[inline(always)]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match self.css_variable_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => match cursor.next().into() {
// A CSS variable followed by a `,` means that there is a fallback
//
// E.g.: `(--my-color,red)`
// ^
Class::Comma => {
cursor.advance_twice(); // Skip the `,`
self.transition::<ParsingFallbackState>().next(cursor)
}
// End of the CSS variable
//
// E.g.: `(--my-color)`
// ^
_ => {
cursor.advance();
match cursor.curr().into() {
// End of an arbitrary variable, must be followed by `)`
Class::CloseParen => self.done(self.start_pos, cursor),
// Invalid arbitrary variable, not ending at `)`
_ => self.restart(),
}
}
},
}
}
}
impl Machine for ArbitraryVariableMachine<ParsingDataTypeState> {
#[inline(always)]
fn reset(&mut self) {}
#[inline(always)]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
let len = cursor.input.len();
while cursor.pos < len {
match cursor.curr().into() {
// Valid data type characters
//
// E.g.: `(length:--my-length)`
// ^
Class::AlphaLower | Class::Dash => {
cursor.advance();
}
// End of the data type
//
// E.g.: `(length:--my-length)`
// ^
Class::Colon => match cursor.next().into() {
Class::Dash => {
cursor.advance();
return self.transition::<ParsingState>().next(cursor);
}
_ => return self.restart(),
},
// Anything else is not a valid character
_ => return self.restart(),
};
}
self.restart()
}
}
impl Machine for ArbitraryVariableMachine<ParsingFallbackState> {
#[inline(always)]
fn reset(&mut self) {
self.bracket_stack.reset();
}
#[inline(always)]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
let len = cursor.input.len();
while cursor.pos < len {
match cursor.curr().into() {
Class::Escape => match cursor.next().into() {
// An escaped whitespace character is not allowed
//
// E.g.: `(--my-\ color)`
// ^^
Class::Whitespace => return self.restart(),
// An escaped character, skip the next character, resume after
//
// E.g.: `(--my-\#color)`
// ^^
_ => cursor.advance_twice(),
},
Class::OpenParen | Class::OpenBracket | Class::OpenCurly => {
if !self.bracket_stack.push(cursor.curr()) {
return self.restart();
}
cursor.advance();
}
Class::CloseParen | Class::CloseBracket | Class::CloseCurly
if !self.bracket_stack.is_empty() =>
{
if !self.bracket_stack.pop(cursor.curr()) {
return self.restart();
}
cursor.advance();
}
// End of an arbitrary variable
Class::CloseParen => return self.done(self.start_pos, cursor),
// Start of a string
Class::Quote => match self.string_machine.next(cursor) {
MachineState::Idle => return self.restart(),
MachineState::Done(_) => cursor.advance(),
},
// A `:` inside of a fallback value is only valid inside of brackets or inside of a
// string. Everywhere else, it's invalid.
//
// E.g.: `(--foo,bar:baz)`
// ^ Not valid
//
// E.g.: `(--url,url(https://example.com))`
// ^ Valid
//
// E.g.: `(--my-content:'a:b:c:')`
// ^ ^ ^ Valid
Class::Colon if self.bracket_stack.is_empty() => return self.restart(),
// Any kind of whitespace is not allowed
Class::Whitespace => return self.restart(),
// String interpolation-like syntax is not allowed. E.g.: `[${x}]`
Class::Dollar if matches!(cursor.next().into(), Class::OpenCurly) => {
return self.restart()
}
// Everything else is valid
_ => cursor.advance(),
};
}
self.restart()
}
}
#[derive(Clone, Copy, PartialEq, ClassifyBytes)]
enum Class {
#[bytes_range(b'a'..=b'z')]
AlphaLower,
#[bytes_range(b'A'..=b'Z')]
AlphaUpper,
#[bytes(b'@')]
At,
#[bytes(b':')]
Colon,
#[bytes(b',')]
Comma,
#[bytes(b'-')]
Dash,
#[bytes(b'.')]
Dot,
#[bytes(b'$')]
Dollar,
#[bytes(b'\\')]
Escape,
#[bytes(b'\0')]
End,
#[bytes_range(b'0'..=b'9')]
Number,
#[bytes(b'[')]
OpenBracket,
#[bytes(b']')]
CloseBracket,
#[bytes(b'(')]
OpenParen,
#[bytes(b')')]
CloseParen,
#[bytes(b'{')]
OpenCurly,
#[bytes(b'}')]
CloseCurly,
#[bytes(b'"', b'\'', b'`')]
Quote,
#[bytes(b'_')]
Underscore,
#[bytes(b' ', b'\t', b'\n', b'\r', b'\x0C')]
Whitespace,
#[fallback]
Other,
}
#[cfg(test)]
mod tests {
use super::ArbitraryVariableMachine;
use crate::extractor::{arbitrary_variable_machine::IdleState, machine::Machine};
use pretty_assertions::assert_eq;
#[test]
#[ignore]
fn test_arbitrary_variable_machine_performance() {
let input = r#"<div class="(--foo) (--my-color,red,blue) (--my-img,url('https://example.com?q=(][)'))"></div>"#.repeat(100);
ArbitraryVariableMachine::<IdleState>::test_throughput(100_000, &input);
ArbitraryVariableMachine::<IdleState>::test_duration_once(&input);
todo!()
}
#[test]
fn test_arbitrary_variable_extraction() {
for (input, expected) in [
// Simple utility
("(--foo)", vec!["(--foo)"]),
// With dashes
("(--my-color)", vec!["(--my-color)"]),
// With a fallback
("(--my-color,red,blue)", vec!["(--my-color,red,blue)"]),
// With a fallback containing a string with unbalanced brackets
(
"(--my-img,url('https://example.com?q=(][)'))",
vec!["(--my-img,url('https://example.com?q=(][)'))"],
),
// With a type hint
("(length:--my-length)", vec!["(length:--my-length)"]),
// --------------------------------------------------------
// Exceptions:
// Arbitrary variable must start with a CSS variable
(r"(bar)", vec![]),
// Arbitrary variables must be valid CSS variables
(r"(--my-\ color)", vec![]),
(r"(--my#color)", vec![]),
// Fallbacks cannot have spaces
(r"(--my-color, red)", vec![]),
// Fallbacks cannot have escaped spaces
(r"(--my-color,\ red)", vec![]),
// Variables must have at least one character after the `--`
(r"(--)", vec![]),
(r"(--,red)", vec![]),
(r"(-)", vec![]),
(r"(-my-color)", vec![]),
] {
assert_eq!(
ArbitraryVariableMachine::<IdleState>::test_extract_all(input),
expected
);
}
}
#[test]
fn test_exceptions() {
for (input, expected) in [
// JS string interpolation
// As part of the variable
("(--my-${var})", vec![]),
// As the fallback
("(--my-variable,${var})", vec![]),
// As the fallback in strings
(
"(--my-variable,url('${var}'))",
vec!["(--my-variable,url('${var}'))"],
),
] {
assert_eq!(
ArbitraryVariableMachine::<IdleState>::test_extract_all(input),
expected
);
}
}
}

View file

@ -0,0 +1,128 @@
use classification_macros::ClassifyBytes;
use crate::extractor::Span;
#[inline(always)]
pub fn is_valid_before_boundary(c: &u8) -> bool {
matches!(c.into(), Class::Common | Class::Before)
}
#[inline(always)]
pub fn is_valid_after_boundary(c: &u8) -> bool {
matches!(c.into(), Class::Common | Class::After)
}
#[inline(always)]
pub fn has_valid_boundaries(span: &Span, input: &[u8]) -> bool {
let before = {
if span.start == 0 {
b'\0'
} else {
input[span.start - 1]
}
};
let after = {
if span.end >= input.len() - 1 {
b'\0'
} else {
input[span.end + 1]
}
};
// Ensure the span has valid boundary characters before and after
is_valid_before_boundary(&before) && is_valid_after_boundary(&after)
}
#[derive(Debug, Clone, Copy, ClassifyBytes)]
enum Class {
// Whitespace, e.g.:
//
// ```
// <div class="flex flex-col items-center"></div>
// ^ ^
// ```
#[bytes(b'\t', b'\n', b'\x0C', b'\r', b' ')]
// Quotes, e.g.:
//
// ```
// <div class="flex">
// ^ ^
// ```
#[bytes(b'"', b'\'', b'`')]
// End of the input, e.g.:
//
// ```
// flex
// ^
// ```
#[bytes(b'\0')]
Common,
// Angular like attributes, e.g.:
//
// ````
// [class.foo]
// ^
// ```
#[bytes(b'.')]
// Twig-like templating languages, e.g.:
//
// ```
// <div class="{% if true %}flex{% else %}block{% endif %}">
// ^
// ```
#[bytes(b'}')]
// XML-like languages where classes are inside the tag, e.g.:
// ```
// <f:case value="0">from-blue-900 to-cyan-200</f:case>
// ^
// ```
#[bytes(b'>')]
Before,
// Clojure and Angular like languages, e.g.:
// ```
// [:div.p-2]
// ^
// [class.foo]
// ^
// ```
#[bytes(b']')]
// Twig like templating languages, e.g.:
//
// ```
// <div class="{% if true %}flex{% else %}block{% endif %}">
// ^
// ```
#[bytes(b'{')]
// Svelte like attributes, e.g.:
//
// ```
// <div class:flex="bool"></div>
// ^
// ```
#[bytes(b'=')]
// Escaped character when embedding one language in another via strings, e.g.:
//
// ```
// $attributes->merge([
// 'x-init' => '$el.classList.add(\'-translate-x-full\'); $el.classList.add(\'transition-transform\')',
// ^ ^
// ]);
// ```
//
// In this case there is some JavaScript embedded in an string in PHP and some of the quotes
// need to be escaped.
#[bytes(b'\\')]
// XML-like languages where classes are inside the tag, e.g.:
// ```
// <f:case value="0">from-blue-900 to-cyan-200</f:case>
// ^
// ```
#[bytes(b'<')]
After,
#[fallback]
Other,
}

View file

@ -0,0 +1,57 @@
const SIZE: usize = 32;
#[repr(C)]
#[derive(Debug, Default)]
pub struct BracketStack {
/// Bracket stack to ensure properly balanced brackets.
bracket_stack: [u8; SIZE],
bracket_stack_len: usize,
}
impl BracketStack {
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.bracket_stack_len == 0
}
#[inline(always)]
pub fn push(&mut self, bracket: u8) -> bool {
if self.bracket_stack_len >= SIZE {
return false;
}
unsafe {
*self.bracket_stack.get_unchecked_mut(self.bracket_stack_len) = match bracket {
b'(' => b')',
b'[' => b']',
b'{' => b'}',
b'<' => b'>',
_ => std::hint::unreachable_unchecked(),
};
}
self.bracket_stack_len += 1;
true
}
#[inline(always)]
pub fn pop(&mut self, bracket: u8) -> bool {
if self.bracket_stack_len == 0 {
return false;
}
self.bracket_stack_len -= 1;
unsafe {
if *self.bracket_stack.get_unchecked(self.bracket_stack_len) != bracket {
return false;
}
}
true
}
#[inline(always)]
pub fn reset(&mut self) {
self.bracket_stack_len = 0;
}
}

View file

@ -0,0 +1,361 @@
use crate::cursor;
use crate::extractor::boundary::{has_valid_boundaries, is_valid_before_boundary};
use crate::extractor::machine::{Machine, MachineState};
use crate::extractor::utility_machine::UtilityMachine;
use crate::extractor::variant_machine::VariantMachine;
use crate::extractor::Span;
/// Extract full candidates including variants and utilities.
#[derive(Debug, Default)]
pub struct CandidateMachine {
/// Start position of the candidate
start_pos: usize,
/// End position of the last variant (if any)
last_variant_end_pos: Option<usize>,
utility_machine: UtilityMachine,
variant_machine: VariantMachine,
}
impl Machine for CandidateMachine {
#[inline(always)]
fn reset(&mut self) {
self.start_pos = 0;
self.last_variant_end_pos = None;
}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
let len = cursor.input.len();
while cursor.pos < len {
// Skip ahead for known characters that will never be part of a candidate. No need to
// run any sub-machines.
if cursor.curr().is_ascii_whitespace() {
self.reset();
cursor.advance();
continue;
}
// Candidates don't start with these characters, so we can skip ahead.
if matches!(cursor.curr(), b':' | b'"' | b'\'' | b'`') {
self.reset();
cursor.advance();
continue;
}
// Jump ahead if the character is known to be an invalid boundary and we should start
// at the next boundary even though "valid" candidates can exist.
//
// E.g.: `<div class="">`
// ^^^ Valid candidate
// ^ But this character makes it invalid
// ^ Therefore we jump here
//
// E.g.: `Some Class`
// ^ ^ Invalid, we can jump ahead to the next boundary
//
if matches!(cursor.curr(), b'<' | b'A'..=b'Z') {
if let Some(offset) = cursor.input[cursor.pos..]
.iter()
.position(|&c| is_valid_before_boundary(&c))
{
self.reset();
cursor.advance_by(offset + 1);
} else {
return self.restart();
}
continue;
}
let mut variant_cursor = cursor.clone();
let variant_machine_state = self.variant_machine.next(&mut variant_cursor);
let mut utility_cursor = cursor.clone();
let utility_machine_state = self.utility_machine.next(&mut utility_cursor);
match (variant_machine_state, utility_machine_state) {
// No variant, but the utility machine completed
(MachineState::Idle, MachineState::Done(utility_span)) => {
cursor.move_to(utility_cursor.pos + 1);
let span = match self.last_variant_end_pos {
Some(end_pos) => {
// Verify that the utility is touching the last variant
if end_pos + 1 != utility_span.start {
return self.restart();
}
Span::new(self.start_pos, utility_span.end)
}
None => utility_span,
};
// Ensure the span has valid boundary characters before and after
if !has_valid_boundaries(&span, cursor.input) {
return self.restart();
}
return self.done_span(span);
}
// Both variant and utility machines are done
// E.g.: `hover:flex`
// ^^^^^^ Variant
// ^^^^^ Utility
//
(MachineState::Done(variant_span), MachineState::Done(utility_span)) => {
cursor.move_to(variant_cursor.pos + 1);
if let Some(end_pos) = self.last_variant_end_pos {
// Verify variant is touching the last variant
if end_pos + 1 != variant_span.start {
return self.restart();
}
} else {
// We know that there is no variant before this one.
//
// Edge case: JavaScript keys should be considered utilities if they are
// not preceded by another variant, and followed by any kind of whitespace
// or the end of the line.
//
// E.g.: `{ underline: true }`
// ^^^^^^^^^^ Variant
// ^^^^^^^^^ Utility (followed by `: `)
let after = cursor.input.get(utility_span.end + 2).unwrap_or(&b'\0');
if after.is_ascii_whitespace() || *after == b'\0' {
cursor.move_to(utility_cursor.pos + 2);
return self.done_span(utility_span);
}
self.start_pos = variant_span.start;
}
self.last_variant_end_pos = Some(variant_cursor.pos);
}
// Variant is done, utility is invalid
(MachineState::Done(variant_span), MachineState::Idle) => {
cursor.move_to(variant_cursor.pos + 1);
if let Some(end_pos) = self.last_variant_end_pos {
if end_pos + 1 > variant_span.start {
self.reset();
return MachineState::Idle;
}
} else {
self.start_pos = variant_span.start;
}
self.last_variant_end_pos = Some(variant_cursor.pos);
}
(MachineState::Idle, MachineState::Idle) => {
// Skip main cursor to the next character after both machines. We already know
// there is no candidate here.
if variant_cursor.pos > cursor.pos || utility_cursor.pos > cursor.pos {
cursor.move_to(variant_cursor.pos.max(utility_cursor.pos));
}
self.reset();
cursor.advance();
}
}
}
MachineState::Idle
}
}
impl CandidateMachine {
#[inline(always)]
fn done_span(&mut self, span: Span) -> MachineState {
self.reset();
MachineState::Done(span)
}
}
#[cfg(test)]
mod tests {
use super::CandidateMachine;
use crate::extractor::machine::Machine;
use pretty_assertions::assert_eq;
#[test]
#[ignore]
fn test_candidate_machine_performance() {
let n = 10_000;
let input = include_str!("../fixtures/example.html");
// let input = &r#"<button type="button" class="absolute -top-1 -left-1.5 flex items-center justify-center p-1.5 text-gray-400 hover:text-gray-500">"#.repeat(100);
CandidateMachine::test_throughput(n, input);
CandidateMachine::test_duration_once(input);
CandidateMachine::test_duration_n(n, input);
todo!()
}
#[test]
fn test_candidate_extraction() {
for (input, expected) in [
// Simple utility
("flex", vec!["flex"]),
// Simple utility with special character(s)
("@container", vec!["@container"]),
// Single character utility
("a", vec!["a"]),
// Simple utility with dashes
("items-center", vec!["items-center"]),
// Simple utility with numbers
("px-2.5", vec!["px-2.5"]),
// Simple variant with simple utility
("hover:flex", vec!["hover:flex"]),
// Arbitrary properties
("[color:red]", vec!["[color:red]"]),
("![color:red]", vec!["![color:red]"]),
("[color:red]!", vec!["[color:red]!"]),
("[color:red]/20", vec!["[color:red]/20"]),
("![color:red]/20", vec!["![color:red]/20"]),
("[color:red]/20!", vec!["[color:red]/20!"]),
// With multiple variants
("hover:focus:flex", vec!["hover:focus:flex"]),
// Exceptions:
//
// Keys inside of a JS object could be a variant-less candidate. Vue example.
("{ underline: true }", vec!["underline", "true"]),
// With complex variants
(
"[&>[data-slot=icon]:last-child]:right-2.5",
vec!["[&>[data-slot=icon]:last-child]:right-2.5"],
),
// With multiple (complex) variants
(
"[&>[data-slot=icon]:last-child]:sm:right-2.5",
vec!["[&>[data-slot=icon]:last-child]:sm:right-2.5"],
),
(
"sm:[&>[data-slot=icon]:last-child]:right-2.5",
vec!["sm:[&>[data-slot=icon]:last-child]:right-2.5"],
),
// Exceptions regarding boundaries
//
// `flex!` is valid, but since it's followed by a non-boundary character it's invalid.
// `block` is therefore also invalid because it didn't start after a boundary.
("flex!block", vec![]),
] {
for (wrapper, additional) in [
// No wrapper
("{}", vec![]),
// With leading spaces
(" {}", vec![]),
(" {}", vec![]),
(" {}", vec![]),
// With trailing spaces
("{} ", vec![]),
("{} ", vec![]),
("{} ", vec![]),
// Surrounded by spaces
(" {} ", vec![]),
// Inside a string
("'{}'", vec![]),
// Inside a function call
("fn('{}')", vec![]),
// Inside nested function calls
("fn1(fn2('{}'))", vec![]),
// --------------------------
//
// HTML
// Inside a class (on its own)
(r#"<div class="{}"></div>"#, vec!["class"]),
// Inside a class (first)
(r#"<div class="{} foo"></div>"#, vec!["class", "foo"]),
// Inside a class (second)
(r#"<div class="foo {}"></div>"#, vec!["class", "foo"]),
// Inside a class (surrounded)
(
r#"<div class="foo {} bar"></div>"#,
vec!["class", "foo", "bar"],
),
// --------------------------
//
// JavaScript
// Inside a variable
(r#"let classes = '{}';"#, vec!["let", "classes"]),
// Inside an object (key)
(
r#"let classes = { '{}': true };"#,
vec!["let", "classes", "true"],
),
// Inside an object (no spaces, key)
(r#"let classes = {'{}':true};"#, vec!["let", "classes"]),
// Inside an object (value)
(
r#"let classes = { primary: '{}' };"#,
vec!["let", "classes", "primary"],
),
// Inside an object (no spaces, value)
(r#"let classes = {primary:'{}'};"#, vec!["let", "classes"]),
] {
let input = wrapper.replace("{}", input);
let mut expected = expected.clone();
expected.extend(additional);
expected.sort();
let mut actual = CandidateMachine::test_extract_all(&input);
actual.sort();
if actual != expected {
dbg!(&input);
}
assert_eq!(actual, expected);
}
}
}
#[test]
fn do_not_consider_svg_path_commands() {
for input in [
r#"<path d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>"#,
r#"<path d="0h2m-2"/>"#,
] {
assert_eq!(
CandidateMachine::test_extract_all(input),
Vec::<&str>::new()
);
}
}
#[test]
fn test_js_interpolation() {
for (input, expected) in [
// Utilities
// Arbitrary value
("bg-[${color}]", vec![]),
// Arbitrary property
("[color:${value}]", vec![]),
("[${key}:value]", vec![]),
("[${key}:${value}]", vec![]),
// Arbitrary property for CSS variables
("[--color:${value}]", vec![]),
("[--color-${name}:value]", vec![]),
// Arbitrary variable
("bg-(--my-${name})", vec![]),
("bg-(--my-variable,${fallback})", vec![]),
(
"bg-(--my-image,url('https://example.com?q=${value}'))",
vec!["bg-(--my-image,url('https://example.com?q=${value}'))"],
),
// Variants
("data-[state=${state}]:flex", vec![]),
("support-(--my-${value}):flex", vec![]),
("support-(--my-variable,${fallback}):flex", vec![]),
("[@media(width>=${value})]:flex", vec![]),
] {
assert_eq!(CandidateMachine::test_extract_all(input), expected);
}
}
}

View file

@ -0,0 +1,214 @@
use crate::cursor;
use crate::extractor::machine::{Machine, MachineState};
use classification_macros::ClassifyBytes;
/// Extract CSS variables from an input.
///
/// E.g.:
///
/// ```text
/// var(--my-variable)
/// ^^^^^^^^^^^^^
/// ```
#[derive(Debug, Default)]
pub struct CssVariableMachine;
impl Machine for CssVariableMachine {
#[inline(always)]
fn reset(&mut self) {}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
// CSS Variables must start with `--`
if Class::Dash != cursor.curr().into() || Class::Dash != cursor.next().into() {
return MachineState::Idle;
}
let start_pos = cursor.pos;
let len = cursor.input.len();
cursor.advance_twice();
while cursor.pos < len {
match cursor.curr().into() {
// https://drafts.csswg.org/css-syntax-3/#ident-token-diagram
//
Class::AllowedCharacter | Class::Dash => {
match cursor.next().into() {
// Valid character followed by a valid character or an escape character
//
// E.g.: `--my-variable`
// ^^
// E.g.: `--my-\#variable`
// ^^
Class::AllowedCharacter | Class::Dash | Class::Escape => cursor.advance(),
// Valid character followed by anything else means the variable is done
//
// E.g.: `'--my-variable'`
// ^
_ => {
// There must be at least 1 character after the `--`
if cursor.pos - start_pos < 2 {
return self.restart();
} else {
return self.done(start_pos, cursor);
}
}
}
}
Class::Escape => match cursor.next().into() {
// An escaped whitespace character is not allowed
//
// In CSS it is allowed, but in the context of a class it's not because then we
// would have spaces in the class.
//
// E.g.: `bg-(--my-\ color)`
// ^
Class::Whitespace => return self.restart(),
// An escape at the end of the class is not allowed
Class::End => return self.restart(),
// An escaped character, skip the next character, resume after
//
// E.g.: `--my-\#variable`
// ^ We are here
// ^ Resume here
_ => cursor.advance_twice(),
},
// Character is not valid anymore
_ => return self.restart(),
}
}
MachineState::Idle
}
}
#[derive(Clone, Copy, PartialEq, ClassifyBytes)]
enum Class {
#[bytes(b'-')]
Dash,
#[bytes(b'_')]
#[bytes_range(b'a'..=b'z', b'A'..=b'Z', b'0'..=b'9')]
// non-ASCII (such as Emoji): https://drafts.csswg.org/css-syntax-3/#non-ascii-ident-code-point
#[bytes_range(0x80..=0xff)]
AllowedCharacter,
#[bytes(b'\\')]
Escape,
#[bytes(b' ', b'\t', b'\n', b'\r', b'\x0C')]
Whitespace,
#[bytes(b'\0')]
End,
#[fallback]
Other,
}
#[cfg(test)]
mod tests {
use super::CssVariableMachine;
use crate::extractor::machine::Machine;
use pretty_assertions::assert_eq;
#[test]
#[ignore]
fn test_css_variable_machine_performance() {
let input = r#"This sentence will contain a few variables here and there var(--my-variable) --other-variable-1\/2 var(--more-variables-here)"#.repeat(100);
CssVariableMachine::test_throughput(100_000, &input);
CssVariableMachine::test_duration_once(&input);
todo!();
}
#[test]
fn test_css_variable_machine_extraction() {
for (input, expected) in [
// Simple variable
("--foo", vec!["--foo"]),
("--my-variable", vec!["--my-variable"]),
// Multiple variables
(
"calc(var(--first) + var(--second))",
vec!["--first", "--second"],
),
// Variables with... emojis
("--😀", vec!["--😀"]),
("--😀-😁", vec!["--😀-😁"]),
// Escaped character in the middle, skips the next character
(r#"--spacing-1\/2"#, vec![r#"--spacing-1\/2"#]),
// Escaped whitespace is not allowed
(r#"--my-\ variable"#, vec![]),
// --------------------------
//
// Exceptions
// Not a valid variable
("", vec![]),
("-", vec![]),
("--", vec![]),
] {
for wrapper in [
// No wrapper
"{}",
// With leading spaces
" {}",
// With trailing spaces
"{} ",
// Surrounded by spaces
" {} ",
// Inside a string
"'{}'",
// Inside a function call
"fn({})",
// Inside nested function calls
"fn1(fn2({}))",
// --------------------------
//
// HTML
// Inside a class (on its own)
r#"<div class="{}"></div>"#,
// Inside a class (first)
r#"<div class="{} foo"></div>"#,
// Inside a class (second)
r#"<div class="foo {}"></div>"#,
// Inside a class (surrounded)
r#"<div class="foo {} bar"></div>"#,
// Inside an arbitrary property
r#"<div class="[{}:red]"></div>"#,
// --------------------------
//
// JavaScript
// Inside a variable
r#"let classes = '{}';"#,
// Inside an object (key)
r#"let classes = { '{}': true };"#,
// Inside an object (no spaces, key)
r#"let classes = {'{}':true};"#,
// Inside an object (value)
r#"let classes = { primary: '{}' };"#,
// Inside an object (no spaces, value)
r#"let classes = {primary:'{}'};"#,
// Inside an array
r#"let classes = ['{}'];"#,
] {
let input = wrapper.replace("{}", input);
let actual = CssVariableMachine::test_extract_all(&input);
if actual != expected {
dbg!(&input);
}
assert_eq!(actual, expected);
}
}
}
}

View file

@ -0,0 +1,161 @@
use crate::cursor;
#[derive(Debug, Clone, Copy)]
pub struct Span {
/// Inclusive start position of the span
pub start: usize,
/// Inclusive end position of the span
pub end: usize,
}
impl Span {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
#[inline(always)]
pub fn slice<'a>(&self, input: &'a [u8]) -> &'a [u8] {
&input[self.start..=self.end]
}
}
#[derive(Debug, Default)]
pub enum MachineState {
/// Machine is not doing anything at the moment
#[default]
Idle,
/// Machine is done parsing and has extracted a span
Done(Span),
}
pub trait Machine: Sized + Default {
fn reset(&mut self);
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState;
/// Reset the state machine, and mark the machine as [MachineState::Idle].
#[inline(always)]
fn restart(&mut self) -> MachineState {
self.reset();
MachineState::Idle
}
/// Reset the state machine, and mark the machine as [MachineState::Done(…)].
#[inline(always)]
fn done(&mut self, start: usize, cursor: &cursor::Cursor<'_>) -> MachineState {
self.reset();
MachineState::Done(Span::new(start, cursor.pos))
}
#[cfg(test)]
fn test_throughput(iterations: usize, input: &str) {
use crate::throughput::Throughput;
use std::hint::black_box;
let input = input.as_bytes();
let len = input.len();
let throughput = Throughput::compute(iterations, len, || {
let mut machine = Self::default();
let mut cursor = cursor::Cursor::new(input);
while cursor.pos < len {
_ = black_box(machine.next(&mut cursor));
cursor.advance();
}
});
eprintln!(
"{}: Throughput: {}",
std::any::type_name::<Self>(),
throughput
);
}
#[cfg(test)]
fn test_duration_once(input: &str) {
use std::hint::black_box;
let input = input.as_bytes();
let len = input.len();
let duration = {
let start = std::time::Instant::now();
let mut machine = Self::default();
let mut cursor = cursor::Cursor::new(input);
while cursor.pos < len {
_ = black_box(machine.next(&mut cursor));
cursor.advance();
}
start.elapsed()
};
eprintln!(
"{}: Duration: {:?}",
std::any::type_name::<Self>(),
duration
);
}
#[cfg(test)]
fn test_duration_n(n: usize, input: &str) {
use std::hint::black_box;
let input = input.as_bytes();
let len = input.len();
let duration = {
let start = std::time::Instant::now();
for _ in 0..n {
let mut machine = Self::default();
let mut cursor = cursor::Cursor::new(input);
while cursor.pos < len {
_ = black_box(machine.next(&mut cursor));
cursor.advance();
}
}
start.elapsed()
};
eprintln!(
"{}: Duration: {:?} ({} iterations, ~{:?} per iteration)",
std::any::type_name::<Self>(),
duration,
n,
duration / n as u32
);
}
#[cfg(test)]
fn test_extract_all(input: &str) -> Vec<&str> {
input
// Mimicking the behavior of how we parse lines individually
.split_terminator("\n")
.flat_map(|input| {
let mut machine = Self::default();
let mut cursor = cursor::Cursor::new(input.as_bytes());
let mut actual: Vec<&str> = vec![];
let len = cursor.input.len();
while cursor.pos < len {
if let MachineState::Done(span) = machine.next(&mut cursor) {
actual.push(unsafe {
std::str::from_utf8_unchecked(span.slice(cursor.input))
});
}
cursor.advance();
}
actual
})
.collect()
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,167 @@
use crate::cursor;
use crate::extractor::arbitrary_value_machine::ArbitraryValueMachine;
use crate::extractor::arbitrary_variable_machine::ArbitraryVariableMachine;
use crate::extractor::machine::{Machine, MachineState};
use classification_macros::ClassifyBytes;
/// Extract modifiers from an input including the `/`.
///
/// E.g.:
///
/// ```text
/// bg-red-500/20
/// ^^^
///
/// bg-red-500/[20%]
/// ^^^^^^
///
/// bg-red-500/(--my-opacity)
/// ^^^^^^^^^^^^^^^
/// ```
#[derive(Debug, Default)]
pub struct ModifierMachine {
arbitrary_value_machine: ArbitraryValueMachine,
arbitrary_variable_machine: ArbitraryVariableMachine,
}
impl Machine for ModifierMachine {
#[inline(always)]
fn reset(&mut self) {}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
// A modifier must start with a `/`, everything else is not a valid start of a modifier
if Class::Slash != cursor.curr().into() {
return MachineState::Idle;
}
let start_pos = cursor.pos;
cursor.advance();
match cursor.curr().into() {
// Start of an arbitrary value:
//
// ```
// bg-red-500/[20%]
// ^^^^^
// ```
Class::OpenBracket => match self.arbitrary_value_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => self.done(start_pos, cursor),
},
// Start of an arbitrary variable:
//
// ```
// bg-red-500/(--my-opacity)
// ^^^^^^^^^^^^^^
// ```
Class::OpenParen => match self.arbitrary_variable_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => self.done(start_pos, cursor),
},
// Start of a named modifier:
//
// ```
// bg-red-500/20
// ^^
// ```
Class::ValidStart => {
let len = cursor.input.len();
while cursor.pos < len {
match cursor.curr().into() {
Class::ValidStart | Class::ValidInside => {
match cursor.next().into() {
// Only valid characters are allowed, if followed by another valid character
Class::ValidStart | Class::ValidInside => cursor.advance(),
// Valid character, but at the end of the modifier, this ends the
// modifier
_ => return self.done(start_pos, cursor),
}
}
// Anything else is invalid, end of the modifier
_ => return self.restart(),
}
}
MachineState::Idle
}
// Anything else is not a valid start of a modifier
_ => MachineState::Idle,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, ClassifyBytes)]
enum Class {
#[bytes_range(b'a'..=b'z', b'A'..=b'Z', b'0'..=b'9')]
ValidStart,
#[bytes(b'-', b'_', b'.')]
ValidInside,
#[bytes(b'[')]
OpenBracket,
#[bytes(b'(')]
OpenParen,
#[bytes(b'/')]
Slash,
#[fallback]
Other,
}
#[cfg(test)]
mod tests {
use super::ModifierMachine;
use crate::extractor::machine::Machine;
use pretty_assertions::assert_eq;
#[test]
#[ignore]
fn test_modifier_machine_performance() {
let input = r#"<button class="group-hover/name:flex bg-red-500/20 text-black/[20%] border-white/(--my-opacity)">"#;
ModifierMachine::test_throughput(1_000_000, input);
ModifierMachine::test_duration_once(input);
todo!()
}
#[test]
fn test_modifier_extraction() {
for (input, expected) in [
// Simple modifier
("foo/bar", vec!["/bar"]),
("foo/bar-baz", vec!["/bar-baz"]),
// Simple modifier with numbers
("foo/20", vec!["/20"]),
// Simple modifier with numbers
("foo/20", vec!["/20"]),
// Arbitrary value
("foo/[20]", vec!["/[20]"]),
// Arbitrary value with CSS variable shorthand
("foo/(--x)", vec!["/(--x)"]),
("foo/(--foo-bar)", vec!["/(--foo-bar)"]),
// --------------------------------------------------------
// Empty arbitrary value is not allowed
("foo/[]", vec![]),
// Empty arbitrary value shorthand is not allowed
("foo/()", vec![]),
// A CSS variable must start with `--` and must have at least a single character
("foo/(-)", vec![]),
("foo/(--)", vec![]),
// Arbitrary value shorthand should be a valid CSS variable
("foo/(--my#color)", vec![]),
] {
assert_eq!(ModifierMachine::test_extract_all(input), expected);
}
}
}

View file

@ -0,0 +1,535 @@
use crate::cursor;
use crate::extractor::arbitrary_value_machine::ArbitraryValueMachine;
use crate::extractor::arbitrary_variable_machine::ArbitraryVariableMachine;
use crate::extractor::boundary::is_valid_after_boundary;
use crate::extractor::machine::{Machine, MachineState};
use classification_macros::ClassifyBytes;
use std::marker::PhantomData;
#[derive(Debug, Default)]
pub struct IdleState;
#[derive(Debug, Default)]
pub struct ParsingState;
/// Extracts named utilities from an input.
///
/// E.g.:
///
/// ```text
/// flex
/// ^^^^
///
/// bg-red-500
/// ^^^^^^^^^^
/// ```
#[derive(Debug, Default)]
pub struct NamedUtilityMachine<State = IdleState> {
/// Start position of the utility
start_pos: usize,
arbitrary_variable_machine: ArbitraryVariableMachine,
arbitrary_value_machine: ArbitraryValueMachine,
_state: PhantomData<State>,
}
impl<State> NamedUtilityMachine<State> {
#[inline(always)]
fn transition<NextState>(&self) -> NamedUtilityMachine<NextState> {
NamedUtilityMachine {
start_pos: self.start_pos,
arbitrary_variable_machine: Default::default(),
arbitrary_value_machine: Default::default(),
_state: PhantomData,
}
}
}
impl Machine for NamedUtilityMachine<IdleState> {
#[inline(always)]
fn reset(&mut self) {}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match cursor.curr().into() {
Class::AlphaLower => match cursor.next().into() {
// Valid single character utility in between quotes
//
// E.g.: `<div class="a"></div>`
// ^
// E.g.: `<div class="a "></div>`
// ^
// E.g.: `<div class=" a"></div>`
// ^
Class::Whitespace | Class::Quote | Class::End => self.done(cursor.pos, cursor),
// Valid start characters
//
// E.g.: `flex`
// ^
_ => {
self.start_pos = cursor.pos;
cursor.advance();
self.transition::<ParsingState>().next(cursor)
}
},
// Valid start characters
//
// E.g.: `@container`
// ^
Class::At => {
self.start_pos = cursor.pos;
cursor.advance();
self.transition::<ParsingState>().next(cursor)
}
// Valid start of a negative utility, if followed by another set of valid
// characters. `@` as a second character is invalid.
//
// E.g.: `-mx-2.5`
// ^^
Class::Dash => match cursor.next().into() {
Class::AlphaLower => {
self.start_pos = cursor.pos;
cursor.advance();
self.transition::<ParsingState>().next(cursor)
}
// A dash should not be followed by anything else
_ => MachineState::Idle,
},
// Everything else, is not a valid start of the utility.
_ => MachineState::Idle,
}
}
}
impl Machine for NamedUtilityMachine<ParsingState> {
#[inline(always)]
fn reset(&mut self) {
self.start_pos = 0;
}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
let len = cursor.input.len();
while cursor.pos < len {
match cursor.curr().into() {
// Followed by a boundary character, we are at the end of the utility.
//
// E.g.: `'flex'`
// ^
// E.g.: `<div class="flex items-center">`
// ^
// E.g.: `[flex]` (Angular syntax)
// ^
// E.g.: `[class.flex.items-center]` (Angular syntax)
// ^
// E.g.: `:div="{ flex: true }"` (JavaScript object syntax)
// ^
Class::AlphaLower | Class::AlphaUpper => {
if is_valid_after_boundary(&cursor.next()) || {
// Or any of these characters
//
// - `:`, because of JS object keys
// - `/`, because of modifiers
// - `!`, because of important
matches!(
cursor.next().into(),
Class::Colon | Class::Slash | Class::Exclamation
)
} {
return self.done(self.start_pos, cursor);
}
// Still valid characters
cursor.advance()
}
Class::Dash => match cursor.next().into() {
// Start of an arbitrary value
//
// E.g.: `bg-[#0088cc]`
// ^^
Class::OpenBracket => {
cursor.advance();
return match self.arbitrary_value_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => self.done(self.start_pos, cursor),
};
}
// Start of an arbitrary variable
//
// E.g.: `bg-(--my-color)`
// ^^
Class::OpenParen => {
cursor.advance();
return match self.arbitrary_variable_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => self.done(self.start_pos, cursor),
};
}
// A dash is a valid character if it is followed by another valid
// character.
//
// E.g.: `flex-`
// ^ Invalid
// E.g.: `flex-!`
// ^ Invalid
// E.g.: `flex-/`
// ^ Invalid
// E.g.: `flex-2`
// ^ Valid
// E.g.: `foo--bar`
// ^ Valid
Class::AlphaLower | Class::AlphaUpper | Class::Number | Class::Dash => {
cursor.advance();
}
// Everything else is invalid
_ => return self.restart(),
},
Class::Underscore => match cursor.next().into() {
// Valid characters _if_ followed by another valid character. These characters are
// only valid inside of the utility but not at the end of the utility.
//
// E.g.: `custom_`
// ^ Invalid
// E.g.: `custom_!`
// ^ Invalid
// E.g.: `custom_/`
// ^ Invalid
// E.g.: `custom_2`
// ^ Valid
//
Class::AlphaLower | Class::AlphaUpper | Class::Number | Class::Underscore => {
cursor.advance();
}
// Followed by a boundary character, we are at the end of the utility.
//
// E.g.: `'flex'`
// ^
// E.g.: `<div class="flex items-center">`
// ^
// E.g.: `[flex]` (Angular syntax)
// ^
// E.g.: `[class.flex.items-center]` (Angular syntax)
// ^
// E.g.: `:div="{ flex: true }"` (JavaScript object syntax)
// ^
_ if is_valid_after_boundary(&cursor.next()) || {
// Or any of these characters
//
// - `:`, because of JS object keys
// - `/`, because of modifiers
// - `!`, because of important
matches!(
cursor.next().into(),
Class::Colon | Class::Slash | Class::Exclamation
)
} =>
{
return self.done(self.start_pos, cursor)
}
// Everything else is invalid
_ => return self.restart(),
},
// A dot must be surrounded by numbers
//
// E.g.: `px-2.5`
// ^^^
Class::Dot => {
if !matches!(cursor.prev().into(), Class::Number) {
return self.restart();
}
if !matches!(cursor.next().into(), Class::Number) {
return self.restart();
}
cursor.advance();
}
// A number must be preceded by a `-`, `.` or another alphanumeric
// character, and can be followed by a `.` or an alphanumeric character or
// dash or underscore.
//
// E.g.: `text-2xs`
// ^^
// `p-2.5`
// ^^
// `bg-red-500`
// ^^
// It can also be followed by a %, but that will be considered the end of
// the candidate.
//
// E.g.: `from-15%`
// ^
//
Class::Number => {
if !matches!(
cursor.prev().into(),
Class::Dash
| Class::Underscore
| Class::Dot
| Class::Number
| Class::AlphaLower
| Class::AlphaUpper
) {
return self.restart();
}
if !matches!(
cursor.next().into(),
Class::Dot
| Class::Number
| Class::AlphaLower
| Class::AlphaUpper
| Class::Percent
| Class::Underscore
| Class::Dash
) {
return self.done(self.start_pos, cursor);
}
cursor.advance();
}
// A percent sign must be preceded by a number.
//
// E.g.:
//
// ```
// from-15%
// ^^
// ```
Class::Percent => {
if !matches!(cursor.prev().into(), Class::Number) {
return self.restart();
}
return self.done(self.start_pos, cursor);
}
// Everything else is invalid
_ => return self.restart(),
};
}
self.restart()
}
}
#[derive(Clone, Copy, ClassifyBytes)]
enum Class {
#[bytes_range(b'a'..=b'z')]
AlphaLower,
#[bytes_range(b'A'..=b'Z')]
AlphaUpper,
#[bytes(b'@')]
At,
#[bytes(b':')]
Colon,
#[bytes(b'-')]
Dash,
#[bytes(b'.')]
Dot,
#[bytes(b'\0')]
End,
#[bytes(b'!')]
Exclamation,
#[bytes_range(b'0'..=b'9')]
Number,
#[bytes(b'[')]
OpenBracket,
#[bytes(b']')]
CloseBracket,
#[bytes(b'(')]
OpenParen,
#[bytes(b'%')]
Percent,
#[bytes(b'"', b'\'', b'`')]
Quote,
#[bytes(b'/')]
Slash,
#[bytes(b'_')]
Underscore,
#[bytes(b' ', b'\t', b'\n', b'\r', b'\x0C')]
Whitespace,
#[fallback]
Other,
}
#[cfg(test)]
mod tests {
use super::{IdleState, NamedUtilityMachine};
use crate::extractor::machine::Machine;
use pretty_assertions::assert_eq;
#[test]
#[ignore]
fn test_named_utility_machine_performance() {
let input = r#"<button class="flex items-center px-2.5 -inset-x-2 bg-[#0088cc] text-(--my-color)">"#;
NamedUtilityMachine::<IdleState>::test_throughput(1_000_000, input);
NamedUtilityMachine::<IdleState>::test_duration_once(input);
todo!()
}
#[test]
fn test_named_utility_extraction() {
for (input, expected) in [
// Simple utility
("flex", vec!["flex"]),
// Simple utility with special character(s)
("@container", vec!["@container"]),
// Simple single-character utility
("a", vec!["a"]),
// With dashes
("items-center", vec!["items-center"]),
// With double dashes
("items--center", vec!["items--center"]),
// With numbers
("px-5", vec!["px-5"]),
("px-2.5", vec!["px-2.5"]),
// Underscores followed by numbers
("header_1", vec!["header_1"]),
("header_1_2", vec!["header_1_2"]),
// With number followed by dash or underscore
("text-title1-strong", vec!["text-title1-strong"]),
("text-title1_strong", vec!["text-title1_strong"]),
// With capital letter followed by number
("text-titleV1-strong", vec!["text-titleV1-strong"]),
// With trailing % sign
("from-15%", vec!["from-15%"]),
// Arbitrary value with bracket notation
("bg-[#0088cc]", vec!["bg-[#0088cc]"]),
// Arbitrary variable
("bg-(--my-color)", vec!["bg-(--my-color)"]),
// Arbitrary variable with fallback
("bg-(--my-color,red,blue)", vec!["bg-(--my-color,red,blue)"]),
// --------------------------------------------------------
// Exceptions:
// Arbitrary variable must be valid
(r"bg-(--my-color\)", vec![]),
(r"bg-(--my#color)", vec![]),
// Single letter utility with uppercase letter is invalid
("A", vec![]),
// A dot must be in-between numbers
("opacity-0.5", vec!["opacity-0.5"]),
("opacity-.5", vec![]),
("opacity-5.", vec![]),
// A number must be preceded by a `-`, `.` or another number
("text-2xs", vec!["text-2xs"]),
// Random invalid utilities
("-$", vec![]),
("-_", vec![]),
("-foo-", vec![]),
("foo-=", vec![]),
("foo-#", vec![]),
("foo-!", vec![]),
("foo-/20", vec![]),
("-", vec![]),
("--", vec![]),
("---", vec![]),
] {
for (wrapper, additional) in [
// No wrapper
("{}", vec![]),
// With leading spaces
(" {}", vec![]),
// With trailing spaces
("{} ", vec![]),
// Surrounded by spaces
(" {} ", vec![]),
// Inside a string
("'{}'", vec![]),
// Inside a function call
("fn('{}')", vec![]),
// Inside nested function calls
("fn1(fn2('{}'))", vec!["fn1", "fn2"]),
// --------------------------
//
// HTML
// Inside a class (on its own)
(r#"<div class="{}"></div>"#, vec!["div", "class"]),
// Inside a class (first)
(r#"<div class="{} foo"></div>"#, vec!["div", "class", "foo"]),
// Inside a class (second)
(r#"<div class="foo {}"></div>"#, vec!["div", "class", "foo"]),
// Inside a class (surrounded)
(
r#"<div class="foo {} bar"></div>"#,
vec!["div", "class", "foo", "bar"],
),
// --------------------------
//
// JavaScript
// Inside a variable
(r#"let classes = '{}';"#, vec!["let", "classes"]),
// Inside an object (key)
(
r#"let classes = { '{}': true };"#,
vec!["let", "classes", "true"],
),
// Inside an object (no spaces, key)
(r#"let classes = {'{}':true};"#, vec!["let", "classes"]),
// Inside an object (value)
(
r#"let classes = { primary: '{}' };"#,
vec!["let", "classes", "primary"],
),
// Inside an object (no spaces, value)
(
r#"let classes = {primary:'{}'};"#,
vec!["let", "classes", "primary"],
),
// Inside an array
(r#"let classes = ['{}'];"#, vec!["let", "classes"]),
] {
let input = wrapper.replace("{}", input);
let mut expected = expected.clone();
expected.extend(additional);
expected.sort();
let mut actual = NamedUtilityMachine::<IdleState>::test_extract_all(&input);
actual.sort();
if actual != expected {
dbg!(&input);
}
assert_eq!(actual, expected);
}
}
}
}

View file

@ -0,0 +1,422 @@
use crate::cursor;
use crate::extractor::arbitrary_value_machine::ArbitraryValueMachine;
use crate::extractor::arbitrary_variable_machine::ArbitraryVariableMachine;
use crate::extractor::machine::{Machine, MachineState};
use crate::extractor::modifier_machine::ModifierMachine;
use classification_macros::ClassifyBytes;
use std::marker::PhantomData;
#[derive(Debug, Default)]
pub struct IdleState;
/// Parsing a variant
#[derive(Debug, Default)]
pub struct ParsingState;
/// Parsing a modifier
///
/// E.g.:
///
/// ```text
/// group-hover/name:
/// ^^^^^
/// ```
///
#[derive(Debug, Default)]
pub struct ParsingModifierState;
/// Parsing the end of a variant
///
/// E.g.:
///
/// ```text
/// hover:
/// ^
/// ```
#[derive(Debug, Default)]
pub struct ParsingEndState;
/// Extract named variants from an input including the `:`.
///
/// E.g.:
///
/// ```text
/// hover:flex
/// ^^^^^^
///
/// data-[state=pending]:flex
/// ^^^^^^^^^^^^^^^^^^^^^
///
/// supports-(--my-variable):flex
/// ^^^^^^^^^^^^^^^^^^^^^^^^^
/// ```
#[derive(Debug, Default)]
pub struct NamedVariantMachine<State = IdleState> {
/// Start position of the variant
start_pos: usize,
arbitrary_variable_machine: ArbitraryVariableMachine,
arbitrary_value_machine: ArbitraryValueMachine,
modifier_machine: ModifierMachine,
_state: PhantomData<State>,
}
impl<State> NamedVariantMachine<State> {
#[inline(always)]
fn transition<NextState>(&self) -> NamedVariantMachine<NextState> {
NamedVariantMachine {
start_pos: self.start_pos,
arbitrary_variable_machine: Default::default(),
arbitrary_value_machine: Default::default(),
modifier_machine: Default::default(),
_state: PhantomData,
}
}
}
impl Machine for NamedVariantMachine<IdleState> {
#[inline(always)]
fn reset(&mut self) {}
#[inline(always)]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match cursor.curr().into() {
Class::AlphaLower | Class::Star => match cursor.next().into() {
// Valid single character variant, must be followed by a `:`
//
// E.g.: `<div class="x:flex"></div>`
// ^^
// E.g.: `*:`
// ^^
Class::Colon => {
cursor.advance();
self.transition::<ParsingEndState>().next(cursor)
}
// Valid start characters
//
// E.g.: `hover:`
// ^
// E.g.: `**:`
// ^
_ => {
self.start_pos = cursor.pos;
cursor.advance();
self.transition::<ParsingState>().next(cursor)
}
},
// Valid start characters
//
// E.g.: `2xl:`
// ^
// E.g.: `@md:`
// ^
Class::Number | Class::At => {
self.start_pos = cursor.pos;
cursor.advance();
self.transition::<ParsingState>().next(cursor)
}
// Everything else, is not a valid start of the variant.
_ => MachineState::Idle,
}
}
}
impl Machine for NamedVariantMachine<ParsingState> {
#[inline(always)]
fn reset(&mut self) {}
#[inline(always)]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
let len = cursor.input.len();
while cursor.pos < len {
match cursor.curr().into() {
Class::Dash => match cursor.next().into() {
// Start of an arbitrary value
//
// E.g.: `data-[state=pending]:`.
// ^^
Class::OpenBracket => {
cursor.advance();
return match self.arbitrary_value_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => self.parse_arbitrary_end(cursor),
};
}
// Start of an arbitrary variable
//
// E.g.: `supports-(--my-color):`.
// ^^
Class::OpenParen => {
cursor.advance();
return match self.arbitrary_variable_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => self.parse_arbitrary_end(cursor),
};
}
// Valid characters _if_ followed by another valid character. These characters are
// only valid inside of the variant but not at the end of the variant.
//
// E.g.: `hover-`
// ^ Invalid
// E.g.: `hover-!`
// ^ Invalid
// E.g.: `hover-/`
// ^ Invalid
// E.g.: `flex-1`
// ^ Valid
Class::Dash
| Class::Underscore
| Class::AlphaLower
| Class::AlphaUpper
| Class::Number => cursor.advance(),
// Everything else is invalid
_ => return self.restart(),
},
// Start of an arbitrary value
//
// E.g.: `@[state=pending]:`.
// ^
Class::OpenBracket => {
return match self.arbitrary_value_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => self.parse_arbitrary_end(cursor),
};
}
Class::Underscore => match cursor.next().into() {
// Valid characters _if_ followed by another valid character. These characters are
// only valid inside of the variant but not at the end of the variant.
//
// E.g.: `hover_`
// ^ Invalid
// E.g.: `hover_!`
// ^ Invalid
// E.g.: `hover_/`
// ^ Invalid
// E.g.: `custom_1`
// ^ Valid
Class::Dash
| Class::Underscore
| Class::AlphaLower
| Class::AlphaUpper
| Class::Number => cursor.advance(),
// Everything else is invalid
_ => return self.restart(),
},
// Still valid characters
Class::AlphaLower | Class::AlphaUpper | Class::Number | Class::Star => {
cursor.advance()
}
// A `/` means we are at the end of the variant, but there might be a modifier
//
// E.g.:
//
// ```
// group-hover/name:
// ^
// ```
Class::Slash => return self.transition::<ParsingModifierState>().next(cursor),
// A `:` means we are at the end of the variant
//
// E.g.: `hover:`
// ^
Class::Colon => return self.done(self.start_pos, cursor),
// A dot must be surrounded by numbers
//
// E.g.: `2.5xl:flex`
// ^^^
Class::Dot => {
if !matches!(cursor.prev().into(), Class::Number) {
return self.restart();
}
if !matches!(cursor.next().into(), Class::Number) {
return self.restart();
}
cursor.advance();
}
// Everything else is invalid
_ => return self.restart(),
};
}
self.restart()
}
}
impl NamedVariantMachine<ParsingState> {
#[inline(always)]
fn parse_arbitrary_end(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match cursor.next().into() {
Class::Slash => {
cursor.advance();
self.transition::<ParsingModifierState>().next(cursor)
}
Class::Colon => {
cursor.advance();
self.transition::<ParsingEndState>().next(cursor)
}
_ => self.restart(),
}
}
}
impl Machine for NamedVariantMachine<ParsingModifierState> {
#[inline(always)]
fn reset(&mut self) {}
#[inline(always)]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match self.modifier_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => match cursor.next().into() {
// Modifier must be followed by a `:`
//
// E.g.: `group-hover/name:`
// ^
Class::Colon => {
cursor.advance();
self.transition::<ParsingEndState>().next(cursor)
}
// Everything else is invalid
_ => self.restart(),
},
}
}
}
impl Machine for NamedVariantMachine<ParsingEndState> {
#[inline(always)]
fn reset(&mut self) {}
#[inline(always)]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match cursor.curr().into() {
// The end of a variant must be the `:`
//
// E.g.: `hover:`
// ^
Class::Colon => self.done(self.start_pos, cursor),
// Everything else is invalid
_ => self.restart(),
}
}
}
#[derive(Clone, Copy, ClassifyBytes)]
enum Class {
#[bytes_range(b'a'..=b'z')]
AlphaLower,
#[bytes_range(b'A'..=b'Z')]
AlphaUpper,
#[bytes(b'@')]
At,
#[bytes(b':')]
Colon,
#[bytes(b'-')]
Dash,
#[bytes(b'.')]
Dot,
#[bytes_range(b'0'..=b'9')]
Number,
#[bytes(b'[')]
OpenBracket,
#[bytes(b'(')]
OpenParen,
#[bytes(b'*')]
Star,
#[bytes(b'/')]
Slash,
#[bytes(b'_')]
Underscore,
#[fallback]
Other,
}
#[cfg(test)]
mod tests {
use super::{IdleState, NamedVariantMachine};
use crate::extractor::machine::Machine;
use pretty_assertions::assert_eq;
#[test]
#[ignore]
fn test_named_variant_machine_performance() {
let input = r#"<button class="hover:focus:flex data-[state=pending]:flex supports-(--my-variable):flex group-hover/named:not-has-peer-data-disabled:flex">"#;
NamedVariantMachine::<IdleState>::test_throughput(1_000_000, input);
NamedVariantMachine::<IdleState>::test_duration_once(input);
todo!()
}
#[test]
fn test_named_variant_extraction() {
for (input, expected) in [
// Simple variant
("hover:", vec!["hover:"]),
// Simple single-character variant
("a:", vec!["a:"]),
("a/foo:", vec!["a/foo:"]),
//
("group-hover:flex", vec!["group-hover:"]),
("group-hover/name:flex", vec!["group-hover/name:"]),
(
"group-[data-state=pending]/name:flex",
vec!["group-[data-state=pending]/name:"],
),
("supports-(--foo)/name:flex", vec!["supports-(--foo)/name:"]),
// Odd media queries
("1.5xl:flex", vec!["1.5xl:"]),
// Container queries
("@md:flex", vec!["@md:"]),
("@max-md:flex", vec!["@max-md:"]),
("@-[36rem]:flex", vec!["@-[36rem]:"]),
("@[36rem]:flex", vec!["@[36rem]:"]),
// --------------------------------------------------------
// Exceptions:
// Arbitrary variable must be valid
(r"supports-(--my-color\):", vec![]),
(r"supports-(--my#color)", vec![]),
// Single letter variant with uppercase letter is invalid
("A:", vec![]),
] {
let actual = NamedVariantMachine::<IdleState>::test_extract_all(input);
if actual != expected {
dbg!(&input);
}
assert_eq!(actual, expected);
}
}
}

View file

@ -0,0 +1,436 @@
use crate::cursor;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
use bstr::ByteSlice;
#[derive(Debug, Default)]
pub struct Clojure;
/// This is meant to be a rough estimate of a valid ClojureScript keyword
///
/// This can be approximated by the following regex:
/// /::?[a-zA-Z0-9!#$%&*+./:<=>?_|-]+/
///
/// However, keywords are intended to be detected as utilities. Since the set
/// of valid characters in a utility (outside of arbitrary values) is smaller,
/// along with the fact that neither `[]` nor `()` are allowed in keywords we
/// can simplify this list quite a bit.
#[inline]
fn is_keyword_character(byte: u8) -> bool {
(matches!(
byte,
b'!' | b'#' | b'%' | b'*' | b'+' | b'-' | b'.' | b'/' | b':' | b'_'
) | byte.is_ascii_alphanumeric())
}
impl PreProcessor for Clojure {
fn process(&self, content: &[u8]) -> Vec<u8> {
let content = content
.replace(":class", " ")
.replace(":className", " ");
let len = content.len();
let mut result = content.to_vec();
let mut cursor = cursor::Cursor::new(&content);
while cursor.pos < len {
match cursor.curr() {
// Consume strings as-is
b'"' => {
result[cursor.pos] = b' ';
cursor.advance();
while cursor.pos < len {
match cursor.curr() {
// Escaped character, skip ahead to the next character
b'\\' => cursor.advance_twice(),
// End of the string
b'"' => {
result[cursor.pos] = b' ';
break;
}
// Everything else is valid
_ => cursor.advance(),
};
}
}
// Discard line comments until the end of the line.
// Comments start with `;;`
b';' if matches!(cursor.next(), b';') => {
while cursor.pos < len && cursor.curr() != b'\n' {
result[cursor.pos] = b' ';
cursor.advance();
}
}
// Consume keyword until a terminating character is reached.
b':' => {
result[cursor.pos] = b' ';
cursor.advance();
while cursor.pos < len {
match cursor.curr() {
// A `.` surrounded by digits is a decimal number, so we don't want to replace it.
//
// E.g.:
// ```
// gap-1.5
// ^
// ```
b'.' if cursor.prev().is_ascii_digit()
&& cursor.next().is_ascii_digit() =>
{
// Keep the `.` as-is
}
// A `.` not surrounded by digits denotes the start of a new class name in a
// dot-delimited keyword.
//
// E.g.:
// ```
// flex.gap-1.5
// ^
// ```
b'.' => {
result[cursor.pos] = b' ';
}
// End of keyword.
_ if !is_keyword_character(cursor.curr()) => {
result[cursor.pos] = b' ';
break;
}
// Consume everything else.
_ => {}
};
cursor.advance();
}
}
// Handle quote with a list, e.g.: `'(…)`
// and with a vector, e.g.: `'[…]`
b'\'' if matches!(cursor.next(), b'[' | b'(') => {
result[cursor.pos] = b' ';
cursor.advance();
result[cursor.pos] = b' ';
let end = match cursor.curr() {
b'[' => b']',
b'(' => b')',
_ => unreachable!(),
};
// Consume until the closing `]`
while cursor.pos < len {
match cursor.curr() {
x if x == end => {
result[cursor.pos] = b' ';
break;
}
// Consume strings as-is
b'"' => {
result[cursor.pos] = b' ';
cursor.advance();
while cursor.pos < len {
match cursor.curr() {
// Escaped character, skip ahead to the next character
b'\\' => cursor.advance_twice(),
// End of the string
b'"' => {
result[cursor.pos] = b' ';
break;
}
// Everything else is valid
_ => cursor.advance(),
};
}
}
_ => {}
};
cursor.advance();
}
}
// Handle quote with a keyword, e.g.: `'bg-white`
b'\'' if !cursor.next().is_ascii_whitespace() => {
result[cursor.pos] = b' ';
cursor.advance();
while cursor.pos < len {
match cursor.curr() {
// End of keyword.
_ if !is_keyword_character(cursor.curr()) => {
result[cursor.pos] = b' ';
break;
}
// Consume everything else.
_ => {}
};
cursor.advance();
}
}
// Aggressively discard everything else, reducing false positives and preventing
// characters surrounding keywords from producing false negatives.
// E.g.:
// ```
// (when condition :bg-white)
// ^
// ```
// A ')' is never a valid part of a keyword, but will nonetheless prevent 'bg-white'
// from being extracted if not discarded.
_ => {
result[cursor.pos] = b' ';
}
};
cursor.advance();
}
result
}
}
#[cfg(test)]
mod tests {
use super::Clojure;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_clojure_pre_processor() {
for (input, expected) in [
(":div.flex-1.flex-2", " div flex-1 flex-2"),
(
":.flex-3.flex-4 ;defaults to div",
" flex-3 flex-4 ",
),
("{:class :flex-5.flex-6", " flex-5 flex-6"),
(r#"{:class "flex-7 flex-8"}"#, r#" flex-7 flex-8 "#),
(
r#"{:class ["flex-9" :flex-10]}"#,
r#" flex-9 flex-10 "#,
),
(
r#"(dom/div {:class "flex-11 flex-12"})"#,
r#" flex-11 flex-12 "#,
),
("(dom/div :.flex-13.flex-14", " flex-13 flex-14"),
(
r#"[:div#hello.bg-white.pr-1.5 {:class ["grid grid-cols-[auto,1fr] grid-rows-2"]}]"#,
r#" div#hello bg-white pr-1.5 grid grid-cols-[auto,1fr] grid-rows-2 "#,
),
] {
Clojure::test(input, expected);
}
}
#[test]
fn test_extract_candidates() {
// https://github.com/luckasRanarison/tailwind-tools.nvim/issues/68#issuecomment-2660951258
let input = r#"
:div.c1.c2
:.c3.c4 ;defaults to div
{:class :c5.c6
{:class "c7 c8"}
{:class ["c9" :c10]}
(dom/div {:class "c11 c12"})
(dom/div :.c13.c14
{:className :c15.c16
{:className "c17 c18"}
{:className ["c19" :c20]}
(dom/div {:className "c21 c22"})
"#;
Clojure::test_extract_contains(
input,
vec![
"c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11", "c12", "c13",
"c14", "c15", "c16", "c17", "c18", "c19", "c20", "c21", "c22",
],
);
// Similar structure but using real classes
let input = r#"
:div.flex-1.flex-2
:.flex-3.flex-4 ;defaults to div
{:class :flex-5.flex-6
{:class "flex-7 flex-8"}
{:class ["flex-9" :flex-10]}
(dom/div {:class "flex-11 flex-12"})
(dom/div :.flex-13.flex-14
{:className :flex-15.flex-16
{:className "flex-17 flex-18"}
{:className ["flex-19" :flex-20]}
(dom/div {:className "flex-21 flex-22"})
"#;
Clojure::test_extract_contains(
input,
vec![
"flex-1", "flex-2", "flex-3", "flex-4", "flex-5", "flex-6", "flex-7", "flex-8",
"flex-9", "flex-10", "flex-11", "flex-12", "flex-13", "flex-14", "flex-15",
"flex-16", "flex-17", "flex-18", "flex-19", "flex-20", "flex-21", "flex-22",
],
);
}
#[test]
fn test_special_characters_are_valid_in_strings() {
// In this case the `:` and `.` should not be replaced by ` ` because they are inside a
// string.
let input = r#"
(dom/div {:class "hover:flex px-1.5"})
"#;
Clojure::test_extract_contains(input, vec!["hover:flex", "px-1.5"]);
}
#[test]
fn test_ignore_comments_with_invalid_strings() {
let input = r#"
;; This is an unclosed string: "
(dom/div {:class "hover:flex px-1.5"})
"#;
Clojure::test_extract_contains(input, vec!["hover:flex", "px-1.5"]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/17760
#[test]
fn test_extraction_of_classes_with_dots() {
let input = r#"
($ :div {:class [:flex :gap-1.5 :p-1]} …)
"#;
Clojure::test_extract_contains(input, vec!["flex", "gap-1.5", "p-1"]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/18336
#[test]
fn test_extraction_of_pseudoclasses_from_keywords() {
let input = r#"
($ :div {:class [:flex :first:lg:pr-6 :first:2xl:pl-6 :group-hover/2:2xs:pt-6]} …)
:.hover:bg-white
[:div#hello.bg-white.pr-1.5]
"#;
Clojure::test_extract_contains(
input,
vec![
"flex",
"first:lg:pr-6",
"first:2xl:pl-6",
"group-hover/2:2xs:pt-6",
"hover:bg-white",
"bg-white",
"pr-1.5",
],
);
}
// https://github.com/tailwindlabs/tailwindcss/issues/18344
#[test]
fn test_noninterference_of_parens_on_keywords() {
let input = r#"
(get props :y-padding :py-5)
($ :div {:class [:flex.pr-1.5 (if condition :bg-white :bg-black)]})
"#;
Clojure::test_extract_contains(
input,
vec!["py-5", "flex", "pr-1.5", "bg-white", "bg-black"],
);
}
// https://github.com/tailwindlabs/tailwindcss/issues/18882
#[test]
fn test_extract_from_symbol_list() {
let input = r#"
[:div {:class '[z-1 z-2
z-3 z-4]}]
"#;
Clojure::test_extract_contains(input, vec!["z-1", "z-2", "z-3", "z-4"]);
// https://github.com/tailwindlabs/tailwindcss/pull/18345#issuecomment-3253403847
let input = r#"
(def hl-class-names '[ring ring-blue-500])
[:div
{:class (cond-> '[input w-full]
textarea? (conj 'textarea)
(seq errors) (concat '[border-red-500 bg-red-100])
highlight? (concat hl-class-names))}]
"#;
Clojure::test_extract_contains(
input,
vec![
"ring",
"ring-blue-500",
"input",
"w-full",
"textarea",
"border-red-500",
"bg-red-100",
],
);
let input = r#"
[:div
{:class '[h-100 lg:h-200 max-w-32 mx-auto py-60
flex flex-col justify-end items-center
lg:flex-row lg:justify-between
bg-cover bg-center bg-no-repeat rounded-3xl overflow-hidden
font-semibold text-gray-900]}]
"#;
Clojure::test_extract_contains(
input,
vec![
"h-100",
"lg:h-200",
"max-w-32",
"mx-auto",
"py-60",
"flex",
"flex-col",
"justify-end",
"items-center",
"lg:flex-row",
"lg:justify-between",
"bg-cover",
"bg-center",
"bg-no-repeat",
"rounded-3xl",
"overflow-hidden",
"font-semibold",
"text-gray-900",
],
);
// `/` is invalid and requires explicit quoting
let input = r#"
'[p-32 "text-black/50"]
"#;
Clojure::test_extract_contains(input, vec!["p-32", "text-black/50"]);
// `[…]` is invalid and requires explicit quoting
let input = r#"
(print '[ring ring-blue-500 "bg-[#0088cc]"])
"#;
Clojure::test_extract_contains(input, vec!["ring", "ring-blue-500", "bg-[#0088cc]"]);
// `'(…)` looks similar to `[…]` but uses parentheses instead of brackets
let input = r#"
(print '(ring ring-blue-500 "bg-[#0088cc]"))
"#;
Clojure::test_extract_contains(input, vec!["ring", "ring-blue-500", "bg-[#0088cc]"]);
}
}

View file

@ -0,0 +1,154 @@
use crate::cursor;
use crate::extractor::bracket_stack::BracketStack;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[derive(Debug, Default)]
pub struct Elixir;
impl PreProcessor for Elixir {
fn process(&self, content: &[u8]) -> Vec<u8> {
let mut cursor = cursor::Cursor::new(content);
let mut result = content.to_vec();
let mut bracket_stack = BracketStack::default();
while cursor.pos < content.len() {
// Look for a sigil marker
if cursor.curr() != b'~' {
cursor.advance();
continue;
}
// Scan charlists, strings, and wordlists
if !matches!(cursor.next(), b'c' | b'C' | b's' | b'S' | b'w' | b'W') {
cursor.advance();
continue;
}
cursor.advance_twice();
// Match the opening for a sigil
if !matches!(cursor.curr(), b'(' | b'[' | b'{') {
continue;
}
// Replace the opening bracket with a space
result[cursor.pos] = b' ';
// Scan until we find a balanced closing one and replace it too
bracket_stack.push(cursor.curr());
while cursor.pos < content.len() {
cursor.advance();
match cursor.curr() {
// Escaped character, skip ahead to the next character
b'\\' => cursor.advance_twice(),
b'(' | b'[' | b'{' => {
bracket_stack.push(cursor.curr());
}
b')' | b']' | b'}' if !bracket_stack.is_empty() => {
bracket_stack.pop(cursor.curr());
if bracket_stack.is_empty() {
// Replace the closing bracket with a space
result[cursor.pos] = b' ';
break;
}
}
_ => {}
}
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::Elixir;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_elixir_pre_processor() {
for (input, expected) in [
// Simple sigils
("~W(flex underline)", "~W flex underline "),
("~W[flex underline]", "~W flex underline "),
("~W{flex underline}", "~W flex underline "),
// Sigils with nested brackets
(
"~W(text-(--my-color) bg-(--my-color))",
"~W text-(--my-color) bg-(--my-color) ",
),
("~W[text-[red] bg-[red]]", "~W text-[red] bg-[red] "),
// Word sigils with modifiers
("~W(flex underline)a", "~W flex underline a"),
("~W(flex underline)c", "~W flex underline c"),
("~W(flex underline)s", "~W flex underline s"),
// Other sigil types
("~w(flex underline)", "~w flex underline "),
("~c(flex underline)", "~c flex underline "),
("~C(flex underline)", "~C flex underline "),
("~s(flex underline)", "~s flex underline "),
("~S(flex underline)", "~S flex underline "),
] {
Elixir::test(input, expected);
}
}
#[test]
fn test_extract_candidates() {
let input = r#"
~W(c1 c2)
~W[c3 c4]
~W{c5 c6}
~W(text-(--c7) bg-(--c8))
~W[text-[c9] bg-[c10]]
~W(c13 c14)a
~W(c15 c16)c
~W(c17 c18)s
~w(c19 c20)
~c(c21 c22)
~C(c23 c24)
~s(c25 c26)
~S(c27 c28)
~W"c29 c30"
~W'c31 c32'
"#;
Elixir::test_extract_contains(
input,
vec![
"c1",
"c2",
"c3",
"c4",
"c5",
"c6",
"text-(--c7)",
"bg-(--c8)",
"c13",
"c14",
"c15",
"c16",
"c17",
"c18",
"c19",
"c20",
"c21",
"c22",
"c23",
"c24",
"c25",
"c26",
"c27",
"c28",
"c29",
"c30",
"c31",
"c32",
],
);
}
}

View file

@ -0,0 +1,616 @@
use crate::cursor;
use crate::extractor::bracket_stack::BracketStack;
use crate::extractor::machine::{Machine, MachineState};
use crate::extractor::pre_processors::pre_processor::PreProcessor;
use crate::extractor::variant_machine::VariantMachine;
use crate::scanner::pre_process_input;
use bstr::ByteVec;
#[derive(Debug, Default)]
pub struct Haml;
impl PreProcessor for Haml {
fn process(&self, content: &[u8]) -> Vec<u8> {
let len = content.len();
let mut result = content.to_vec();
let mut cursor = cursor::Cursor::new(content);
let mut bracket_stack = BracketStack::default();
// Haml Comments: -#
// https://haml.info/docs/yardoc/file.REFERENCE.html#ruby-evaluation
//
// > The hyphen followed immediately by the pound sign signifies a silent comment. Any text
// > following this isn’t rendered in the resulting document at all.
//
// ```haml
// %p foo
// -# This is a comment
// %p bar
// ```
//
// > You can also nest text beneath a silent comment. None of this text will be rendered.
//
// ```haml
// %p foo
// -#
// This won't be displayed
// Nor will this
// Nor will this.
// %p bar
// ```
//
// Ruby Evaluation
// https://haml.info/docs/yardoc/file.REFERENCE.html#ruby-evaluation
//
// When any of the following characters are the first non-whitespace character on the line,
// then the line is treated as Ruby code:
//
// - Inserting Ruby: =
// https://haml.info/docs/yardoc/file.REFERENCE.html#inserting_ruby
//
// ```haml
// %p
// = ['hi', 'there', 'reader!'].join " "
// = "yo"
// ```
//
// - Running Ruby: -
// https://haml.info/docs/yardoc/file.REFERENCE.html#running-ruby--
//
// ```haml
// - foo = "hello"
// - foo << " there"
// - foo << " you!"
// %p= foo
// ```
//
// - Whitespace Preservation: ~
// https://haml.info/docs/yardoc/file.REFERENCE.html#tilde
//
// > ~ works just like =, except that it runs Haml::Helpers.preserve on its input.
//
// ```haml
// ~ "Foo\n<pre>Bar\nBaz</pre>"
// ```
//
// Important note:
//
// > A line of Ruby code can be stretched over multiple lines as long as each line but the
// > last ends with a comma.
//
// ```haml
// - links = {:home => "/",
// :docs => "/docs",
// :about => "/about"}
// ```
//
// Ruby Blocks:
// https://haml.info/docs/yardoc/file.REFERENCE.html#ruby-blocks
//
// > Ruby blocks, like XHTML tags, don’t need to be explicitly closed in Haml. Rather,
// > they’re automatically closed, based on indentation. A block begins whenever the
// > indentation is increased after a Ruby evaluation command. It ends when the indentation
// > decreases (as long as it’s not an else clause or something similar).
//
// ```haml
// - (42...47).each do |i|
// %p= i
// %p See, I can count!
// ```
//
let mut last_newline_position = 0;
while cursor.pos < len {
match cursor.curr() {
// Escape the next character
b'\\' => {
cursor.advance_twice();
continue;
}
// Track the last newline position
b'\n' => {
last_newline_position = cursor.pos;
cursor.advance();
continue;
}
// Skip HAML comments. `-#`
b'-' if cursor.input[last_newline_position..cursor.pos]
.iter()
.all(u8::is_ascii_whitespace)
&& matches!(cursor.next(), b'#') =>
{
// Just consume the comment
let updated_last_newline_position =
self.skip_indented_block(&mut cursor, last_newline_position);
// Override the last known newline position
last_newline_position = updated_last_newline_position;
}
// Skip HTML comments. `/`
b'/' if cursor.input[last_newline_position..cursor.pos]
.iter()
.all(u8::is_ascii_whitespace) =>
{
// Just consume the comment
let updated_last_newline_position =
self.skip_indented_block(&mut cursor, last_newline_position);
// Override the last known newline position
last_newline_position = updated_last_newline_position;
}
// Ruby evaluation
b'-' | b'=' | b'~'
if cursor.input[last_newline_position..cursor.pos]
.iter()
.all(u8::is_ascii_whitespace) =>
{
let mut start = cursor.pos;
let end = self.skip_indented_block(&mut cursor, last_newline_position);
// Increment start with 1 character to skip the `=` or `-` character
start += 1;
let ruby_code = &cursor.input[start..end];
// Override the last known newline position
last_newline_position = end;
let replaced = pre_process_input(ruby_code.to_vec(), "rb");
result.replace_range(start..end, replaced);
}
// Only replace `.` with a space if it's not surrounded by numbers. E.g.:
//
// ```diff
// - .flex.items-center
// + flex items-center
// ```
//
// But with numbers, it's allowed:
//
// ```diff
// - px-2.5
// + px-2.5
// ```
b'.' => {
// Don't replace dots with spaces when inside of any type of brackets, because
// this could be part of arbitrary values. E.g.: `bg-[url(https://example.com)]`
// ^
if !bracket_stack.is_empty() {
cursor.advance();
continue;
}
// If the dot is surrounded by digits, we want to keep it. E.g.: `px-2.5`
// EXCEPT if it's followed by a valid variant that happens to start with a
// digit.
// E.g.: `bg-red-500.2xl:flex`
// ^^^
if cursor.prev().is_ascii_digit() && cursor.next().is_ascii_digit() {
let mut next_cursor = cursor.clone();
next_cursor.advance();
let mut variant_machine = VariantMachine::default();
if let MachineState::Done(_) = variant_machine.next(&mut next_cursor) {
result[cursor.pos] = b' ';
}
} else {
result[cursor.pos] = b' ';
}
}
// Handle Ruby syntax with `%w[]` arrays embedded in Haml attribute hashes. E.g.:
//
// ```haml
// %div{class: %w[bg-blue-500 w-10 h-10]}
// ```
//
// A `%` that follows a value is not a percent literal. E.g.: the `50%w` in
// `hit rate 50%w.`
b'%' if matches!(cursor.next(), b'w' | b'W')
&& !cursor.prev().is_ascii_alphanumeric()
&& !matches!(cursor.prev(), b'_' | b')' | b']' | b'}') =>
{
// Boundary characters
let (open, close) = match cursor.input.get(cursor.pos + 2) {
Some(b'[') => (b'[', b']'),
Some(b'(') => (b'(', b')'),
Some(b'{') => (b'{', b'}'),
Some(b'<') => (b'<', b'>'),
// Any other ASCII punctuation can be used as a custom delimiter
Some(&c) if c.is_ascii_punctuation() => (c, c),
// Everything else is not a valid delimiter
_ => {
cursor.advance();
continue;
}
};
result[cursor.pos] = b' '; // Replace `%`
cursor.advance();
result[cursor.pos] = b' '; // Replace `w`
cursor.advance();
result[cursor.pos] = b' '; // Replace the opening delimiter
cursor.advance();
// Paired delimiters can be nested as long as they are balanced. E.g.:
// `%w[foo[bar]baz]` produces a flat array.
let mut depth = 1_usize;
while cursor.pos < len {
match cursor.curr() {
// Skip escaped characters, unless the backslash is the delimiter
// itself
b'\\' if close != b'\\' => {
// Use backslash to embed spaces in the strings.
if cursor.next() == b' ' {
result[cursor.pos] = b' ';
}
cursor.advance();
}
// Start of a nested delimiter pair
c if c == open && open != close => depth += 1,
// Closing delimiter
c if c == close => {
depth -= 1;
// End of the literal, replace the closing delimiter with a space
if depth == 0 {
result[cursor.pos] = b' ';
break;
}
}
// Everything else is valid content
_ => {}
}
cursor.advance();
}
}
// Replace following characters with spaces if they are not inside of brackets
b'#' | b'=' if bracket_stack.is_empty() => {
result[cursor.pos] = b' ';
}
b'(' | b'[' | b'{' => {
// Replace first bracket with a space
if bracket_stack.is_empty() {
result[cursor.pos] = b' ';
}
bracket_stack.push(cursor.curr());
}
b')' | b']' | b'}' if !bracket_stack.is_empty() => {
bracket_stack.pop(cursor.curr());
// Replace closing bracket with a space
if bracket_stack.is_empty() {
result[cursor.pos] = b' ';
}
}
// Consume everything else
_ => {}
};
cursor.advance();
}
result
}
}
impl Haml {
fn skip_indented_block(
&self,
cursor: &mut cursor::Cursor,
last_known_newline_position: usize,
) -> usize {
let len = cursor.input.len();
// Special case: if the first character of the block is `=`, then newlines are only allowed
// _if_ the last character of the previous line is a comma `,`.
//
// https://haml.info/docs/yardoc/file.REFERENCE.html#inserting_ruby
//
// > A line of Ruby code can be stretched over multiple lines as long as each line but the
// > last ends with a comma. For example:
//
// ```haml
// = link_to_remote "Add to cart",
// :url => { :action => "add", :id => product.id },
// :update => { :success => "cart", :failure => "error" }
// ```
let evaluation_type = cursor.curr();
let block_indentation_level = cursor
.pos
.saturating_sub(last_known_newline_position)
.saturating_sub(1); /* The newline itself */
let mut last_newline_position = last_known_newline_position;
// Consume until the end of the line first
while cursor.pos < len && cursor.curr() != b'\n' {
cursor.advance();
}
// Block is already done, aka just a line
if evaluation_type == b'=' && cursor.prev() != b',' {
return cursor.pos;
}
'outer: while cursor.pos < len {
match cursor.curr() {
// Escape the next character
b'\\' => {
cursor.advance_twice();
continue;
}
// Track the last newline position
b'\n' => {
last_newline_position = cursor.pos;
// We are done with this block
if evaluation_type == b'=' && cursor.prev() != b',' {
break;
}
cursor.advance();
continue;
}
// Skip whitespace and compute the indentation level
x if x.is_ascii_whitespace() => {
// Find first non-whitespace character
while cursor.pos < len && cursor.curr().is_ascii_whitespace() {
if cursor.curr() == b'\n' {
last_newline_position = cursor.pos;
if evaluation_type == b'=' && cursor.prev() != b',' {
// We are done with this block
break 'outer;
}
}
cursor.advance();
}
let indentation = cursor
.pos
.saturating_sub(last_newline_position)
.saturating_sub(1); /* The newline itself */
if indentation < block_indentation_level {
// We are done with this block
break;
}
}
// Not whitespace, end of block
_ => break,
};
cursor.advance();
}
// We didn't find a newline, we reached the end of the input
if last_known_newline_position == last_newline_position {
return cursor.pos;
}
// Move the cursor to the last newline position
cursor.move_to(last_newline_position);
last_newline_position
}
}
#[cfg(test)]
mod tests {
use super::Haml;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
use pretty_assertions::assert_eq;
#[test]
fn test_haml_pre_processor() {
for (input, expected) in [
// Element with classes
(
"%body.flex.flex-col.items-center.justify-center",
"%body flex flex-col items-center justify-center",
),
// Plain element
(
".text-slate-500.xl:text-gray-500",
" text-slate-500 xl:text-gray-500",
),
// Element with hash attributes
(
".text-black.xl:text-red-500{ data: { tailwind: 'css' } }",
" text-black xl:text-red-500 data: { tailwind: 'css' } ",
),
// Element with a boolean attribute
(
".text-green-500.xl:text-blue-500(data-sidebar)",
" text-green-500 xl:text-blue-500 data-sidebar ",
),
// Element with interpreted content
(
".text-yellow-500.xl:text-purple-500= 'Element with interpreted content'",
" text-yellow-500 xl:text-purple-500 'Element with interpreted content'",
),
// Element with a hash at the end and an extra class.
(
".text-orange-500.xl:text-pink-500{ class: 'bg-slate-100' }",
" text-orange-500 xl:text-pink-500 class: 'bg-slate-100' ",
),
// Object reference
(
".text-teal-500.xl:text-indigo-500[@user, :greeting]",
" text-teal-500 xl:text-indigo-500 @user, :greeting ",
),
// Element with an ID
(
".text-lime-500.xl:text-emerald-500#root",
" text-lime-500 xl:text-emerald-500 root",
),
// Dots in strings in HTML attributes stay as-is
(r#"<div id="px-2.5"></div>"#, r#"<div id "px-2.5"></div>"#),
] {
Haml::test(input, expected);
}
}
#[test]
fn test_strings_only_occur_when_nested() {
let input = r#"
%p.mt-2.text-xl
The quote in the next word, can't be the start of a string
%h3.mt-24.text-center.text-4xl.font-bold.italic
The classes above should be extracted
"#;
Haml::test_extract_contains(
input,
vec![
// First paragraph
"mt-2",
"text-xl",
// second paragraph
"mt-24",
"text-center",
"text-4xl",
"font-bold",
"italic",
],
);
}
// https://github.com/tailwindlabs/tailwindcss/issues/20386
#[test]
fn test_embedded_ruby_percent_w_delimiters() {
for (input, expected) in [
// %w[…] in an attribute hash
(
"%div{class: %w[flex px-2.5]}",
"%div class: flex px-2.5 ",
),
// %w<…>
(
"%div{class: %w<flex px-2.5>}",
"%div class: flex px-2.5 ",
),
// Nested `<…>` does not end the literal
(
"%div{class: %w<flex <nested> px-2.5>}",
"%div class: flex <nested> px-2.5 ",
),
// Custom delimiters
(
"%div{class: %w|flex px-2.5|}",
"%div class: flex px-2.5 ",
),
(
"%div{class: %W!flex px-2.5!}",
"%div class: flex px-2.5 ",
),
(
"%div{class: %w#text-sm leading-6#}",
"%div class: text-sm leading-6 ",
),
(
"%div{class: %w=italic tracking-wide=}",
"%div class: italic tracking-wide ",
),
// Nested paired delimiters stay balanced inside the literal
(
"%div{class: %w[content-['[hello]'] p-4]}",
"%div class: content-['[hello]'] p-4 ",
),
// Escaped spaces embed a space in a single array element
(
r#"%div{class: %w[foo\ bar baz-1]}"#,
r#"%div class: foo bar baz-1 "#,
),
// A `%` that follows a value is not a percent literal
("%p hit rate 50%w.", "%p hit rate 50%w "),
] {
Haml::test(input, expected);
}
let input = r#"
%div{class: %w[bg-blue-500 w-10 h-10]}
%div{class: %w<flex px-2.5>}
%div{class: %w|underline font-bold|}
- classes = %w<mt-4 grid>
"#;
Haml::test_extract_contains(
input,
vec![
"bg-blue-500",
"w-10",
"h-10",
"flex",
"px-2.5",
"underline",
"font-bold",
"mt-4",
"grid",
],
);
}
// https://github.com/tailwindlabs/tailwindcss/pull/17051#issuecomment-2711181352
#[test]
fn test_haml_full_file_17051() {
let actual = Haml::extract_annotated(include_bytes!("./test-fixtures/haml/src-17051.haml"));
let expected = include_str!("./test-fixtures/haml/dst-17051.haml");
assert_eq!(actual.replace("\r\n", "\n"), expected.replace("\r\n", "\n"));
}
// https://github.com/tailwindlabs/tailwindcss/issues/17813
#[test]
fn test_haml_full_file_17813() {
let actual = Haml::extract_annotated(include_bytes!("./test-fixtures/haml/src-17813.haml"));
let expected = include_str!("./test-fixtures/haml/dst-17813.haml");
assert_eq!(actual.replace("\r\n", "\n"), expected.replace("\r\n", "\n"));
}
#[test]
fn test_arbitrary_code_followed_by_classes() {
let input = r#"
%p
= i < 3
.flex.items-center
"#;
Haml::test_extract_contains(input, vec!["flex", "items-center"]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/17379#issuecomment-2910108646
#[test]
fn test_crash_missing_newline() {
// The empty `""` will introduce a newline
let good = ["- index = 0", "- index += 1", ""].join("\n");
Haml::test_extract_contains(&good, vec!["index"]);
// This used to crash before the fix
let bad = ["- index = 0", "- index += 1"].join("\n");
Haml::test_extract_contains(&bad, vec!["index"]);
}
}

View file

@ -0,0 +1,63 @@
use crate::cursor;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[derive(Debug, Default)]
pub struct Json;
impl PreProcessor for Json {
fn process(&self, content: &[u8]) -> Vec<u8> {
let len = content.len();
let mut result = content.to_vec();
let mut cursor = cursor::Cursor::new(content);
while cursor.pos < len {
match cursor.curr() {
// Consume strings as-is
b'"' => {
cursor.advance();
while cursor.pos < len {
match cursor.curr() {
// Escaped character, skip ahead to the next character
b'\\' => cursor.advance_twice(),
// End of the string
b'"' => break,
// Everything else is valid
_ => cursor.advance(),
};
}
}
// Replace brackets and curlies with spaces
b'[' | b'{' | b']' | b'}' => {
result[cursor.pos] = b' ';
}
// Consume everything else
_ => {}
};
cursor.advance();
}
result
}
}
#[cfg(test)]
mod tests {
use super::Json;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_json_pre_processor() {
let (input, expected) = (
r#"[1,[2,[3,4,["flex flex-1 content-['hello_world']"]]], {"flex": true}]"#,
r#" 1, 2, 3,4, "flex flex-1 content-['hello_world']" , "flex": true "#,
);
Json::test(input, expected);
}
}

View file

@ -0,0 +1,83 @@
use crate::cursor;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[derive(Debug, Default)]
pub struct Markdown;
impl PreProcessor for Markdown {
fn process(&self, content: &[u8]) -> Vec<u8> {
let len = content.len();
let mut result = content.to_vec();
let mut cursor = cursor::Cursor::new(content);
let mut bracket_stack = vec![];
let mut in_directive = false;
while cursor.pos < len {
match (in_directive, cursor.curr()) {
(false, b'{') => {
result[cursor.pos] = b' ';
in_directive = true;
}
(true, b'(' | b'[' | b'{' | b'<') => {
bracket_stack.push(cursor.curr());
}
(true, b')' | b']' | b'}' | b'>') if !bracket_stack.is_empty() => {
bracket_stack.pop();
}
(true, b'}') => {
result[cursor.pos] = b' ';
in_directive = false;
}
(true, b'.') if bracket_stack.is_empty() => {
result[cursor.pos] = b' ';
}
_ => {}
}
cursor.advance();
}
result
}
}
#[cfg(test)]
mod tests {
use super::Markdown;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_markdown_pre_processor() {
for (input, expected) in [
// Convert dots to spaces inside markdown inline directives
(
":span[Some Text]{.text-gray-500}",
":span[Some Text] text-gray-500 ",
),
(
":span[Some Text]{.text-gray-500.bg-red-500}",
":span[Some Text] text-gray-500 bg-red-500 ",
),
(
":span[Some Text]{#myId .my-class key=val key2='val 2'}",
":span[Some Text] #myId my-class key=val key2='val 2' ",
),
] {
Markdown::test(input, expected);
}
}
#[test]
fn test_nested_classes_keep_the_dots() {
for (input, expected) in [
(
r#"{<div class="px-2.5"></div>}"#,
r#" <div class="px-2.5"></div> "#,
),
(r#"{content-['example.js']}"#, r#" content-['example.js'] "#),
] {
Markdown::test(input, expected);
}
}
}

View file

@ -0,0 +1,31 @@
pub mod clojure;
pub mod elixir;
pub mod haml;
pub mod json;
pub mod markdown;
pub mod pre_processor;
pub mod pug;
pub mod razor;
pub mod ruby;
pub mod rust;
pub mod slim;
pub mod svelte;
pub mod template_toolkit;
pub mod twig;
pub mod vue;
pub use clojure::*;
pub use elixir::*;
pub use haml::*;
pub use json::*;
pub use markdown::*;
pub use pre_processor::*;
pub use pug::*;
pub use razor::*;
pub use ruby::*;
pub use rust::*;
pub use slim::*;
pub use svelte::*;
pub use template_toolkit::*;
pub use twig::*;
pub use vue::*;

View file

@ -0,0 +1,186 @@
pub trait PreProcessor: Sized + Default {
fn process(&self, content: &[u8]) -> Vec<u8>;
#[cfg(test)]
fn test(input: &str, expected: &str) {
use pretty_assertions::assert_eq;
let input = input.as_bytes();
let expected = expected.as_bytes();
let processor = Self::default();
let actual = processor.process(input);
// Convert to strings for better error messages.
let input = String::from_utf8_lossy(input);
let actual = String::from_utf8_lossy(&actual);
let expected = String::from_utf8_lossy(expected);
// The input and output should have the exact same length.
assert_eq!(input.len(), actual.len());
assert_eq!(actual.len(), expected.len());
assert_eq!(actual, expected);
}
#[cfg(test)]
fn test_extract_exact(input: &str, expected: Vec<&str>) {
use crate::extractor::{Extracted, Extractor};
let input = input.as_bytes();
let processor = Self::default();
let transformed = processor.process(input);
let extracted = Extractor::new(&transformed).extract();
// Extract all candidates and css variables.
let candidates = extracted
.iter()
.filter_map(|x| match x {
Extracted::Candidate(bytes) => std::str::from_utf8(bytes).ok(),
Extracted::CssVariable(bytes) => std::str::from_utf8(bytes).ok(),
})
.collect::<Vec<_>>();
if candidates != expected {
dbg!(&candidates, &expected);
panic!("Extracted candidates do not match expected candidates");
}
}
#[cfg(test)]
fn test_extract_contains(input: &str, expected: Vec<&str>) {
use crate::extractor::{Extracted, Extractor};
let input = input.as_bytes();
let processor = Self::default();
let transformed = processor.process(input);
let extracted = Extractor::new(&transformed).extract();
// Extract all candidates and css variables.
let candidates = extracted
.iter()
.filter_map(|x| match x {
Extracted::Candidate(bytes) => std::str::from_utf8(bytes).ok(),
Extracted::CssVariable(bytes) => std::str::from_utf8(bytes).ok(),
})
.collect::<Vec<_>>();
// Ensure all items are present in the candidates.
let mut missing = vec![];
for item in &expected {
if !candidates.contains(item) {
missing.push(item);
}
}
if !missing.is_empty() {
dbg!(&candidates, &missing);
panic!("Missing some items");
}
}
#[cfg(test)]
fn extract_annotated(input: &[u8]) -> String {
use crate::extractor::{Extracted, Extractor};
use std::collections::BTreeMap;
use unicode_width::UnicodeWidthStr;
let processor = Self::default();
let transformed = processor.process(input);
let extracted = Extractor::new(&transformed).extract();
// Extract only candidate positions
let byte_ranges = extracted
.iter()
.filter_map(|x| match x {
Extracted::Candidate(bytes) => {
let start = bytes.as_ptr() as usize - transformed.as_ptr() as usize;
let end = start + bytes.len();
Some((start, end))
}
_ => None,
})
.collect::<Vec<_>>();
// Convert byte ranges to (line, start_col, end_col)
let mut annotations = byte_ranges
.into_iter()
.map(|(start, end)| {
let (line, start_col) = byte_offset_to_line_and_column(input, start);
let (_, end_col) = byte_offset_to_line_and_column(input, end);
(line, start_col, end_col)
})
.collect::<Vec<_>>();
// Sort for safe insertion
annotations.sort_by(|a, b| b.0.cmp(&a.0).then(b.1.cmp(&a.1)));
// Convert input to lines
let mut lines = std::str::from_utf8(input)
.expect("Input must be valid UTF-8")
.lines()
.map(|line| line.to_string())
.collect::<Vec<_>>();
// Group annotations per line
let mut grouped = BTreeMap::<usize, Vec<(usize, usize)>>::new();
for (line, start_char, end_char) in annotations {
grouped
.entry(line)
.or_default()
.push((start_char, end_char));
}
// Inject annotation lines
for (line_idx, spans) in grouped.into_iter().rev() {
let display_line = &lines[line_idx];
let width = UnicodeWidthStr::width(display_line.as_str());
let mut annotation = vec![' '; width];
for (start, end) in spans {
for i in start..end.min(annotation.len()) {
annotation[i] = '^';
}
}
let annotation_line: String = annotation
.into_iter()
.collect::<String>()
.trim_end()
.to_owned();
lines.insert(line_idx + 1, annotation_line);
}
lines.join("\n").trim_end().to_string() + "\n"
}
}
#[cfg(test)]
fn byte_offset_to_line_and_column(input: &[u8], offset: usize) -> (usize, usize) {
use unicode_width::UnicodeWidthStr;
let mut line_start = 0;
let mut line = 0;
for (i, &b) in input.iter().enumerate() {
if i >= offset {
break;
}
if b == b'\n' {
line += 1;
line_start = i + 1;
}
}
let slice = &input[line_start..offset];
let column = std::str::from_utf8(slice).expect("Valid UTF-8");
let column = UnicodeWidthStr::width(column);
(line, column)
}

View file

@ -0,0 +1,181 @@
use crate::cursor;
use crate::extractor::bracket_stack::BracketStack;
use crate::extractor::machine::{Machine, MachineState};
use crate::extractor::pre_processors::pre_processor::PreProcessor;
use crate::extractor::variant_machine::VariantMachine;
#[derive(Debug, Default)]
pub struct Pug;
impl PreProcessor for Pug {
fn process(&self, content: &[u8]) -> Vec<u8> {
let len = content.len();
let mut result = content.to_vec();
let mut cursor = cursor::Cursor::new(content);
let mut bracket_stack = BracketStack::default();
while cursor.pos < len {
match cursor.curr() {
// Only replace `.` with a space if it's not surrounded by numbers. E.g.:
//
// ```diff
// - .flex.items-center
// + flex items-center
// ```
//
// But with numbers, it's allowed:
//
// ```diff
// - px-2.5
// + px-2.5
// ```
b'.' => {
// Don't replace dots with spaces when inside of any type of brackets, because
// this could be part of arbitrary values. E.g.: `bg-[url(https://example.com)]`
// ^
if !bracket_stack.is_empty() {
cursor.advance();
continue;
}
// If the dot is surrounded by digits, we want to keep it. E.g.: `px-2.5`
// EXCEPT if it's followed by a valid variant that happens to start with a
// digit.
// E.g.: `bg-red-500.2xl:flex`
// ^^^
if cursor.prev().is_ascii_digit() && cursor.next().is_ascii_digit() {
let mut next_cursor = cursor.clone();
next_cursor.advance();
let mut variant_machine = VariantMachine::default();
if let MachineState::Done(_) = variant_machine.next(&mut next_cursor) {
result[cursor.pos] = b' ';
}
} else {
result[cursor.pos] = b' ';
}
}
// In Pug the class name shorthand can be followed by a parenthesis. E.g.:
//
// ```pug
// body.border-t-4.p-8(attr=value)
// ^ Not part of the p-8 class
// ```
//
// This means that we need to replace all these `(` and `)` with spaces to make
// sure that we can extract the `p-8`.
//
// However, we also need to make sure that we keep the parens that are part of the
// utility class. E.g.: `bg-(--my-color)`.
b'(' if bracket_stack.is_empty() && !matches!(cursor.prev(), b'-' | b'/') => {
result[cursor.pos] = b' ';
bracket_stack.push(cursor.curr());
}
b'(' | b'[' | b'{' => {
bracket_stack.push(cursor.curr());
}
b')' | b']' | b'}' if !bracket_stack.is_empty() => {
bracket_stack.pop(cursor.curr());
}
// Consume everything else
_ => {}
};
cursor.advance();
}
result
}
}
#[cfg(test)]
mod tests {
use super::Pug;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_pug_pre_processor() {
for (input, expected) in [
// Convert dots to spaces
("div.flex.bg-red-500", "div flex bg-red-500"),
(".flex.bg-red-500", " flex bg-red-500"),
// Keep dots in strings
(r#"div(class="px-2.5")"#, r#"div class="px-2.5")"#),
// Nested brackets
(
"bg-[url(https://example.com/?q=[1,2])]",
"bg-[url(https://example.com/?q=[1,2])]",
),
// Classes in HTML attributes
(r#"<div id="px-2.5"></div>"#, r#"<div id="px-2.5"></div>"#),
] {
Pug::test(input, expected);
}
}
#[test]
fn test_strings_only_occur_when_nested() {
let input = r#"
p.mt-2.text-xl
div The quote in the next word, can't be the start of a string
h3.mt-24.text-center.text-4xl.font-bold.italic
div The classes above should be extracted
"#;
Pug::test_extract_contains(
input,
vec![
// First paragraph
"mt-2",
"text-xl",
// second paragraph
"mt-24",
"text-center",
"text-4xl",
"font-bold",
"italic",
],
);
}
#[test]
fn test_arbitrary_code_followed_by_classes() {
let input = r#"
- i < 3
.flex.items-center
"#;
Pug::test_extract_contains(input, vec!["flex", "items-center"]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/17313
#[test]
fn test_class_shorthand_followed_by_parens() {
let input = r#"
.text-sky-600.bg-neutral-900(title="A tooltip") This div has an HTML attribute.
"#;
Pug::test_extract_contains(input, vec!["text-sky-600", "bg-neutral-900"]);
// Additional test with CSS Variable shorthand syntax in the attribute itself because `(`
// and `)` are not valid in the class shorthand version.
//
// Also included an arbitrary value including `(` and `)` to make sure that we don't
// accidentally remove those either.
let input = r#"
.p-8(class="bg-(--my-color) bg-(--my-color)/(--my-opacity) bg-[url(https://example.com)]")
"#;
Pug::test_extract_contains(
input,
vec![
"p-8",
"bg-(--my-color)",
"bg-(--my-color)/(--my-opacity)",
"bg-[url(https://example.com)]",
],
);
}
}

View file

@ -0,0 +1,42 @@
use crate::extractor::pre_processors::pre_processor::PreProcessor;
use bstr::ByteSlice;
#[derive(Debug, Default)]
pub struct Razor;
impl PreProcessor for Razor {
fn process(&self, content: &[u8]) -> Vec<u8> {
content.replace("@@", " @").replace(r#"@("@")"#, " @")
}
}
#[cfg(test)]
mod tests {
use super::Razor;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_razor_pre_processor() {
let (input, expected) = (
r#"<div class="@@sm:text-red-500">"#,
r#"<div class=" @sm:text-red-500">"#,
);
Razor::test(input, expected);
Razor::test_extract_contains(input, vec!["@sm:text-red-500"]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/17424
#[test]
fn test_razor_syntax_with() {
let (input, expected) = (
r#"<p class="@("@")md:bg-red-500 @@md:border-green-500 border-8">With 2 elements</p>"#,
r#"<p class=" @md:bg-red-500 @md:border-green-500 border-8">With 2 elements</p>"#,
);
Razor::test(input, expected);
Razor::test_extract_contains(
input,
vec!["@md:bg-red-500", "@md:border-green-500", "border-8"],
);
}
}

View file

@ -0,0 +1,528 @@
// See: - https://docs.ruby-lang.org/en/3.4/syntax/literals_rdoc.html#label-Percent+Literals
// - https://docs.ruby-lang.org/en/3.4/syntax/literals_rdoc.html#label-25w+and+-25W-3A+String-Array+Literals
use crate::cursor;
use crate::extractor::bracket_stack;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
use crate::scanner::pre_process_input;
use bstr::ByteVec;
use regex::{Regex, RegexBuilder};
use std::sync;
static TEMPLATE_START_REGEX: sync::LazyLock<Regex> = sync::LazyLock::new(|| {
RegexBuilder::new(r#"\s*([a-z0-9_-]+)_template\s*<<[-~]?([A-Z]+)$"#)
.multi_line(true)
.build()
.unwrap()
});
static TEMPLATE_END_REGEX: sync::LazyLock<Regex> = sync::LazyLock::new(|| {
RegexBuilder::new(r#"^\s*([A-Z]+)"#)
.multi_line(true)
.build()
.unwrap()
});
#[derive(Debug, Default)]
pub struct Ruby;
impl PreProcessor for Ruby {
fn process(&self, content: &[u8]) -> Vec<u8> {
let len = content.len();
let mut result = content.to_vec();
let mut cursor = cursor::Cursor::new(content);
let mut bracket_stack = bracket_stack::BracketStack::default();
// Extract embedded template languages
// https://viewcomponent.org/guide/templates.html#interpolations
// Only process if content is valid UTF-8, otherwise skip HEREDOC extraction
// but still perform the byte-level Ruby processing below
if let Ok(content_as_str) = std::str::from_utf8(content) {
let starts = TEMPLATE_START_REGEX
.captures_iter(content_as_str)
.collect::<Vec<_>>();
let ends = TEMPLATE_END_REGEX
.captures_iter(content_as_str)
.collect::<Vec<_>>();
for start in starts.iter() {
// The language for this block
let lang = start.get(1).unwrap().as_str();
// The HEREDOC delimiter
let delimiter_start = start.get(2).unwrap().as_str();
// Where the "body" starts for the HEREDOC block
let body_start = start.get(0).unwrap().end();
// Look through all of the ends to find a matching language
for end in ends.iter() {
// 1. This must appear after the start
let body_end = end.get(0).unwrap().start();
if body_end < body_start {
continue;
}
// The languages must match otherwise we haven't found the end
let delimiter_end = end.get(1).unwrap().as_str();
if delimiter_end != delimiter_start {
continue;
}
let body = &content_as_str[body_start..body_end];
let replaced =
pre_process_input(body.as_bytes().to_vec(), &lang.to_ascii_lowercase());
result.replace_range(body_start..body_end, replaced);
break;
}
}
}
// Ruby extraction
while cursor.pos < len {
match cursor.curr() {
b'"' => {
cursor.advance();
while cursor.pos < len {
match cursor.curr() {
// Escaped character, skip ahead to the next character
b'\\' => cursor.advance_twice(),
// End of the string
b'"' => break,
// Everything else is valid
_ => cursor.advance(),
};
}
cursor.advance();
continue;
}
b'\'' => {
cursor.advance();
while cursor.pos < len {
match cursor.curr() {
// Escaped character, skip ahead to the next character
b'\\' => cursor.advance_twice(),
// End of the string
b'\'' => break,
// Everything else is valid
_ => cursor.advance(),
};
}
cursor.advance();
continue;
}
// Replace comments in Ruby files
//
// Except for strict locals, these are defined in a `<%# locals: … %>`. Checking if
// the comment is preceded by a `%` should be enough without having to perform more
// parsing logic. Worst case we _do_ scan a few comments.
//
// We also want to skip interpolation syntax, which look like `#{…}`.
b'#' if !matches!(cursor.prev(), b'%') && !matches!(cursor.next(), b'{') => {
result[cursor.pos] = b' ';
cursor.advance();
while cursor.pos < len {
match cursor.curr() {
// End of the comment
b'\n' => break,
// Everything else is part of the comment and replaced
_ => {
result[cursor.pos] = b' ';
cursor.advance();
}
};
}
cursor.advance();
continue;
}
_ => {}
}
// Looking for `%w`, `%W`, or `%p`
if cursor.curr() != b'%' || !matches!(cursor.next(), b'w' | b'W' | b'p') {
cursor.advance();
continue;
}
// A `%` that follows a value is a modulo operation, not a percent literal. E.g.: the
// `50%w` in `hit rate 50%w.`
if cursor.prev().is_ascii_alphanumeric()
|| matches!(cursor.prev(), b'_' | b')' | b']' | b'}')
{
cursor.advance();
continue;
}
cursor.advance_twice();
// Boundary character
let boundary = match cursor.curr() {
b'[' => b']',
b'(' => b')',
b'{' => b'}',
b'<' => b'>',
b' ' => b'\n',
// Any other ASCII punctuation can be used as a custom delimiter
c if c.is_ascii_punctuation() => c,
// Everything else is not a valid delimiter
_ => {
cursor.advance();
continue;
}
};
bracket_stack.reset();
// Replace the current character with a space
result[cursor.pos] = b' ';
// Skip the boundary character
cursor.advance();
while cursor.pos < len {
match cursor.curr() {
// Skip escaped characters, unless the backslash is the delimiter itself
b'\\' if boundary != b'\\' => {
// Use backslash to embed spaces in the strings.
if cursor.next() == b' ' {
result[cursor.pos] = b' ';
}
cursor.advance();
}
// Start of a nested bracket
b'[' | b'(' | b'{' => {
bracket_stack.push(cursor.curr());
}
// Start of a nested `<…>`, which Ruby allows inside a `%w<…>` literal
b'<' if boundary == b'>' => {
bracket_stack.push(cursor.curr());
}
// End of a nested bracket
b']' | b')' | b'}' if !bracket_stack.is_empty() => {
if !bracket_stack.pop(cursor.curr()) {
// Unbalanced
cursor.advance();
}
}
// End of a nested `<…>`
b'>' if boundary == b'>' && !bracket_stack.is_empty() => {
if !bracket_stack.pop(cursor.curr()) {
// Unbalanced
cursor.advance();
}
}
// End of the pattern, replace the boundary character with a space
_ if cursor.curr() == boundary => {
if boundary != b'\n' {
result[cursor.pos] = b' ';
}
break;
}
// Everything else is valid
_ => {}
}
cursor.advance();
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::Ruby;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_ruby_pre_processor() {
for (input, expected) in [
// %w[…]
("%w[flex px-2.5]", "%w flex px-2.5 "),
(
"%w[flex data-[state=pending]:bg-[#0088cc] flex-col]",
"%w flex data-[state=pending]:bg-[#0088cc] flex-col ",
),
// %w{…}
("%w{flex px-2.5}", "%w flex px-2.5 "),
(
"%w{flex data-[state=pending]:bg-(--my-color) flex-col}",
"%w flex data-[state=pending]:bg-(--my-color) flex-col ",
),
// %w(…)
("%w(flex px-2.5)", "%w flex px-2.5 "),
(
"%w(flex data-[state=pending]:bg-(--my-color) flex-col)",
"%w flex data-[state=pending]:bg-(--my-color) flex-col ",
),
// %w<…>
("%w<flex px-2.5>", "%w flex px-2.5 "),
(
"%w<flex data-[state=pending]:bg-(--my-color) flex-col>",
"%w flex data-[state=pending]:bg-(--my-color) flex-col ",
),
// Nested `<…>` does not end the literal
("%w<flex <nested> px-2.5>", "%w flex <nested> px-2.5 "),
// %w|…|, %w:…:, %w!…!
("%w|flex px-2.5|", "%w flex px-2.5 "),
("%w:flex px-2.5:", "%w flex px-2.5 "),
("%w!flex px-2.5!", "%w flex px-2.5 "),
(r#"%w\flex px-2.5\"#, r#"%w flex px-2.5 "#),
// A `%` that follows a value is a modulo operation, not a percent literal
(
"hit rate 50%w.\n%w[flex px-2.5]",
"hit rate 50%w.\n%w flex px-2.5 ",
),
// %w …\n
("%w flex px-2.5\n", "%w flex px-2.5\n"),
// Use backslash to embed spaces in the strings.
(r#"%w[foo\ bar baz\ bat]"#, r#"%w foo bar baz bat "#),
(r#"%W[foo\ bar baz\ bat]"#, r#"%W foo bar baz bat "#),
// The nested delimiters evaluated to a flat array of strings
// (not nested array).
(r#"%w[foo[bar baz]qux]"#, r#"%w foo[bar baz]qux "#),
(
"# test\n# test\n# {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!]\n%w[flex px-2.5]",
" \n \n \n%w flex px-2.5 "
),
(r#""foo # bar""#, r#""foo # bar""#),
(r#"'foo # bar'"#, r#"'foo # bar'"#),
(
r#"def call = tag.span "Foo", class: %w[rounded-full h-0.75 w-0.75]"#,
r#"def call = tag.span "Foo", class: %w rounded-full h-0.75 w-0.75 "#
),
(r#"%w[foo ' bar]"#, r#"%w foo ' bar "#),
(r#"%w[foo " bar]"#, r#"%w foo " bar "#),
(r#"%W[foo ' bar]"#, r#"%W foo ' bar "#),
(r#"%W[foo " bar]"#, r#"%W foo " bar "#),
(r#"%p foo ' bar "#, r#"%p foo ' bar "#),
(r#"%p foo " bar "#, r#"%p foo " bar "#),
(
"%p has a ' quote\n# this should be removed\n%p has a ' quote",
"%p has a ' quote\n \n%p has a ' quote"
),
(
"%p has a \" quote\n# this should be removed\n%p has a \" quote",
"%p has a \" quote\n \n%p has a \" quote"
),
(
"%w#this text is kept# # this text is not",
"%w this text is kept ",
),
] {
Ruby::test(input, expected);
}
}
#[test]
fn test_ruby_extraction() {
for (input, expected) in [
// %w[…]
("%w[flex px-2.5]", vec!["flex", "px-2.5"]),
("%w[px-2.5 flex]", vec!["flex", "px-2.5"]),
("%w[2xl:flex]", vec!["2xl:flex"]),
(
"%w[flex data-[state=pending]:bg-[#0088cc] flex-col]",
vec!["flex", "data-[state=pending]:bg-[#0088cc]", "flex-col"],
),
// %w{…}
("%w{flex px-2.5}", vec!["flex", "px-2.5"]),
("%w{px-2.5 flex}", vec!["flex", "px-2.5"]),
("%w{2xl:flex}", vec!["2xl:flex"]),
(
"%w{flex data-[state=pending]:bg-(--my-color) flex-col}",
vec!["flex", "data-[state=pending]:bg-(--my-color)", "flex-col"],
),
// %w(…)
("%w(flex px-2.5)", vec!["flex", "px-2.5"]),
("%w(px-2.5 flex)", vec!["flex", "px-2.5"]),
("%w(2xl:flex)", vec!["2xl:flex"]),
(
"%w(flex data-[state=pending]:bg-(--my-color) flex-col)",
vec!["flex", "data-[state=pending]:bg-(--my-color)", "flex-col"],
),
// %w<…>
("%w<flex px-2.5>", vec!["flex", "px-2.5"]),
("%w<px-2.5 flex>", vec!["flex", "px-2.5"]),
("%w<2xl:flex>", vec!["2xl:flex"]),
(
"%w<flex data-[state=pending]:bg-(--my-color) flex-col>",
vec!["flex", "data-[state=pending]:bg-(--my-color)", "flex-col"],
),
// Nested `<…>` does not end the literal
("%w<flex <nested> px-2.5>", vec!["flex", "px-2.5"]),
// %w|…|, %w:…:, %w!…!
("%w|flex px-2.5|", vec!["flex", "px-2.5"]),
("%w:flex px-2.5:", vec!["flex", "px-2.5"]),
("%w!flex px-2.5!", vec!["flex", "px-2.5"]),
(
"# test\n# test\n# {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!]\n%w[flex px-2.5]",
vec!["flex", "px-2.5"],
),
(r#""foo # bar""#, vec!["foo", "bar"]),
(r#"'foo # bar'"#, vec!["foo", "bar"]),
(r#"%w[foo ' bar]"#, vec!["foo", "bar"]),
] {
Ruby::test_extract_contains(input, expected);
}
}
// https://github.com/tailwindlabs/tailwindcss/issues/17334
#[test]
fn test_embedded_slim_extraction() {
let input = r#"
class QweComponent < ApplicationComponent
slim_template <<~SLIM
button.rounded-full.bg-red-500
| Some text
button.rounded-full(
class="flex"
)
| Some text
SLIM
end
"#;
Ruby::test_extract_contains(input, vec!["rounded-full", "bg-red-500", "flex"]);
// Embedded Svelte just to verify that we properly pick up the `{x}_template`
let input = r#"
class QweComponent < ApplicationComponent
svelte_template <<~HTML
<div class:flex="true"></div>
HTML
end
"#;
Ruby::test_extract_contains(input, vec!["flex"]);
// Together in the same file
let input = r#"
class QweComponent < ApplicationComponent
slim_template <<~SLIM
button.z-1.z-2
| Some text
SLIM
end
class QweComponent < ApplicationComponent
svelte_template <<~HTML
<div class:z-3="true"></div>
HTML
end
"#;
Ruby::test_extract_contains(input, vec!["z-1", "z-2", "z-3"]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/19728
#[test]
fn test_interpolated_expressions() {
let input = r#"
def width_class(width = nil)
<<~STYLE_CLASS
#{width || 'w-100'}
STYLE_CLASS
end
"#;
Ruby::test_extract_contains(input, vec!["w-100"]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/19239
#[test]
fn test_skip_comments() {
let input = r#"
# From activerecord-8.1.1/lib/active_record/errors.rb:147
# Rails uses RDoc cross-reference syntax in inline documentation:
# {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!]
"#;
// Nothing should be extracted from comments, so expect an empty array.
Ruby::test_extract_exact(input, vec![]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/19481
#[test]
fn test_strict_locals() {
// Strict locals are defined in a `<%# locals: … %>`, but the `#` looks like a comment
// which we should not ignore in this case.
let input = r#"
<%# locals: (css: "text-amber-600") %>
<% more_css = "text-sky-500" %>
<p class="text-green-500">
In a partial
</p>
<p class="<%= css %>">
In a partial using explicit local variables
</p>
<p class="<%= more_css %>">
In a partial using explicit local variables
</p>
"#;
Ruby::test_extract_contains(
input,
vec!["text-amber-600", "text-sky-500", "text-green-500"],
);
}
#[test]
fn test_invalid_utf8_does_not_panic() {
// Invalid UTF-8 sequence: 0x80 is a continuation byte without a leading byte
let invalid_utf8: &[u8] = &[0x80, 0x81, 0x82];
let processor = Ruby::default();
// Should not panic, just return the input unchanged
let result = processor.process(invalid_utf8);
assert_eq!(result, invalid_utf8);
}
#[test]
fn test_valid_utf8_with_multibyte_chars() {
// Test that valid UTF-8 with multi-byte characters (like em-dashes) works
let input = "# Comment with em—dash\n%w[flex px-2.5]";
Ruby::test_extract_contains(input, vec!["flex", "px-2.5"]);
}
}

View file

@ -0,0 +1,238 @@
use crate::extractor::bracket_stack;
use crate::extractor::cursor;
use crate::extractor::machine::Machine;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
use crate::extractor::variant_machine::VariantMachine;
use crate::extractor::MachineState;
use bstr::ByteSlice;
#[derive(Debug, Default)]
pub struct Rust;
impl PreProcessor for Rust {
fn process(&self, content: &[u8]) -> Vec<u8> {
// Leptos support: https://github.com/tailwindlabs/tailwindcss/pull/18093
let replaced_content = content
.replace(" class:", " class ")
.replace("\tclass:", " class ")
.replace("\nclass:", " class ");
if replaced_content.contains_str(b"html!") {
self.process_maud_templates(&replaced_content)
} else {
replaced_content
}
}
}
impl Rust {
fn process_maud_templates(&self, replaced_content: &[u8]) -> Vec<u8> {
let len = replaced_content.len();
let mut result = replaced_content.to_vec();
let mut cursor = cursor::Cursor::new(replaced_content);
let mut bracket_stack = bracket_stack::BracketStack::default();
while cursor.pos < len {
match cursor.curr() {
// Escaped character, skip ahead to the next character
b'\\' => {
cursor.advance_twice();
continue;
}
// Consume strings as-is
b'"' => {
result[cursor.pos] = b' ';
cursor.advance();
while cursor.pos < len {
match cursor.curr() {
// Escaped character, skip ahead to the next character
b'\\' => cursor.advance_twice(),
// End of the string
b'"' => {
result[cursor.pos] = b' ';
break;
}
// Everything else is valid
_ => cursor.advance(),
};
}
}
// Only replace `.` with a space if it's not surrounded by numbers. E.g.:
//
// ```diff
// - .flex.items-center
// + flex items-center
// ```
//
// But with numbers, it's allowed:
//
// ```diff
// - px-2.5
// + px-2.5
// ```
b'.' => {
// Don't replace dots with spaces when inside of any type of brackets, because
// this could be part of arbitrary values. E.g.: `bg-[url(https://example.com)]`
// ^
if !bracket_stack.is_empty() {
cursor.advance();
continue;
}
// If the dot is surrounded by digits, we want to keep it. E.g.: `px-2.5`
// EXCEPT if it's followed by a valid variant that happens to start with a
// digit.
// E.g.: `bg-red-500.2xl:flex`
// ^^^
if cursor.prev().is_ascii_digit() && cursor.next().is_ascii_digit() {
let mut next_cursor = cursor.clone();
next_cursor.advance();
let mut variant_machine = VariantMachine::default();
if let MachineState::Done(_) = variant_machine.next(&mut next_cursor) {
result[cursor.pos] = b' ';
}
} else {
result[cursor.pos] = b' ';
}
}
b'[' => {
bracket_stack.push(cursor.curr());
// Handle `p.flex[condition]`. If there is a `-` before it, it will likely be an
// arbitrary value e.g. `text-[red]`
if !matches!(cursor.prev(), b'-') {
result[cursor.pos] = b' ';
}
}
b']' if !bracket_stack.is_empty() => {
bracket_stack.pop(cursor.curr());
}
// Consume everything else
_ => {}
};
cursor.advance();
}
result
}
}
#[cfg(test)]
mod tests {
use super::Rust;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_leptos_extraction() {
for (input, expected) in [
// Spaces
(
"<div class:flex class:px-2.5={condition()}>",
"<div class flex class px-2.5={condition()}>",
),
// Tabs
(
"<div\tclass:flex class:px-2.5={condition()}>",
"<div class flex class px-2.5={condition()}>",
),
// Newlines
(
"<div\nclass:flex class:px-2.5={condition()}>",
"<div class flex class px-2.5={condition()}>",
),
] {
Rust::test(input, expected);
}
}
// https://github.com/tailwindlabs/tailwindcss/issues/18984
#[test]
fn test_maud_template_extraction() {
let input = r#"
use maud::{html, Markup};
pub fn main() -> Markup {
html! {
header.px-8.py-4.text-black {
"Hello, world!"
}
}
}
"#;
Rust::test_extract_contains(input, vec!["px-8", "py-4", "text-black"]);
// https://maud.lambda.xyz/elements-attributes.html#classes-and-ids-foo-bar
let input = r#"
html! {
input #cannon .big.scary.bright-red type="button" value="Launch Party Cannon";
}
"#;
Rust::test_extract_contains(input, vec!["big", "scary", "bright-red"]);
let input = r#"
html! {
div."bg-[#0088cc]" { "Quotes for backticks" }
}
"#;
Rust::test_extract_contains(input, vec!["bg-[#0088cc]"]);
let input = r#"
html! {
#main {
"Main content!"
.tip { "Storing food in a refrigerator can make it 20% cooler." }
}
}
"#;
Rust::test_extract_contains(input, vec!["tip"]);
let input = r#"
html! {
div."bg-[url(https://example.com)]" { "Arbitrary values" }
}
"#;
Rust::test_extract_contains(input, vec!["bg-[url(https://example.com)]"]);
let input = r#"
html! {
div.px-4.text-black {
"Some text, with unbalanced brackets ]["
}
div.px-8.text-white {
"Some more text, with unbalanced brackets ]["
}
}
"#;
Rust::test_extract_contains(input, vec!["px-4", "text-black", "px-8", "text-white"]);
let input = r#"html! { \x.px-4.text-black { } }"#;
Rust::test(input, r#"html! { \x px-4 text-black { } }"#);
}
// https://github.com/tailwindlabs/tailwindcss/issues/20233
#[test]
fn test_maud_template_extraction_with_conditional_classes() {
let input = r#"
use maud::{html, Markup};
pub fn main() -> Markup {
html! {
p.text-black[cuteness > 50] { "Squee!" }
}
}
"#;
Rust::test_extract_contains(input, vec!["text-black"]);
}
}

View file

@ -0,0 +1,449 @@
use crate::cursor;
use crate::extractor::bracket_stack::BracketStack;
use crate::extractor::machine::{Machine, MachineState};
use crate::extractor::pre_processors::pre_processor::PreProcessor;
use crate::extractor::variant_machine::VariantMachine;
#[derive(Debug, Default)]
pub struct Slim;
impl PreProcessor for Slim {
fn process(&self, content: &[u8]) -> Vec<u8> {
let len = content.len();
let mut result = content.to_vec();
let mut cursor = cursor::Cursor::new(content);
let mut bracket_stack = BracketStack::default();
while cursor.pos < len {
match cursor.curr() {
// Only replace `.` with a space if it's not surrounded by numbers. E.g.:
//
// ```diff
// - .flex.items-center
// + flex items-center
// ```
//
// But with numbers, it's allowed:
//
// ```diff
// - px-2.5
// + px-2.5
// ```
b'.' => {
// Don't replace dots with spaces when inside of any type of brackets, because
// this could be part of arbitrary values. E.g.: `bg-[url(https://example.com)]`
// ^
if !bracket_stack.is_empty() {
cursor.advance();
continue;
}
// If the dot is surrounded by digits, we want to keep it. E.g.: `px-2.5`
// EXCEPT if it's followed by a valid variant that happens to start with a
// digit.
// E.g.: `bg-red-500.2xl:flex`
// ^^^
if cursor.prev().is_ascii_digit() && cursor.next().is_ascii_digit() {
let mut next_cursor = cursor.clone();
next_cursor.advance();
let mut variant_machine = VariantMachine::default();
if let MachineState::Done(_) = variant_machine.next(&mut next_cursor) {
result[cursor.pos] = b' ';
}
} else {
result[cursor.pos] = b' ';
}
}
// Handle Ruby syntax with `%w[]` arrays embedded in Slim directly.
//
// E.g.:
//
// ```
// div [
// class=%w[bg-blue-500 w-10 h-10]
// ]
// ```
//
// A `%` that follows a value is not a percent literal. E.g.: the `50%w` in
// `hit rate 50%w.`
b'%' if matches!(cursor.next(), b'w' | b'W')
&& !cursor.prev().is_ascii_alphanumeric()
&& !matches!(cursor.prev(), b'_' | b')' | b']' | b'}') =>
{
// Boundary characters
let (open, close) = match cursor.input.get(cursor.pos + 2) {
Some(b'[') => (b'[', b']'),
Some(b'(') => (b'(', b')'),
Some(b'{') => (b'{', b'}'),
Some(b'<') => (b'<', b'>'),
// Any other ASCII punctuation can be used as a custom delimiter
Some(&c) if c.is_ascii_punctuation() => (c, c),
// Everything else is not a valid delimiter
_ => {
cursor.advance();
continue;
}
};
result[cursor.pos] = b' '; // Replace `%`
cursor.advance();
result[cursor.pos] = b' '; // Replace `w`
cursor.advance();
result[cursor.pos] = b' '; // Replace the opening delimiter
cursor.advance();
// Paired delimiters can be nested as long as they are balanced. E.g.:
// `%w[foo[bar]baz]` produces a flat array.
let mut depth = 1_usize;
while cursor.pos < len {
match cursor.curr() {
// Skip escaped characters, unless the backslash is the delimiter
// itself
b'\\' if close != b'\\' => {
// Use backslash to embed spaces in the strings.
if cursor.next() == b' ' {
result[cursor.pos] = b' ';
}
cursor.advance();
}
// Start of a nested delimiter pair
c if c == open && open != close => depth += 1,
// Closing delimiter
c if c == close => {
depth -= 1;
// End of the literal, replace the closing delimiter with a space
if depth == 0 {
result[cursor.pos] = b' ';
break;
}
}
// Everything else is valid content
_ => {}
}
cursor.advance();
}
}
// Any `[` preceded by an alphanumeric value will not be part of a candidate.
//
// E.g.:
//
// ```
// .text-xl.text-red-600[
// ^ not part of the `text-red-600` candidate
// data-foo="bar"
// ]
// | This line should be red
// ```
//
// We know that `-[` is valid for an arbitrary value and that `:[` is valid as a
// variant. However `[color:red]` is also valid, in this case `[` will be preceded
// by nothing or a boundary character.
// Instead of listing all boundary characters, let's list the characters we know
// will be invalid instead.
b'[' if bracket_stack.is_empty()
&& matches!(cursor.prev(), b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9') =>
{
result[cursor.pos] = b' ';
bracket_stack.push(cursor.curr());
}
// In Slim the class name shorthand can be followed by a parenthesis. E.g.:
//
// ```slim
// body.border-t-4.p-8(attr=value)
// ^ Not part of the p-8 class
// ```
//
// This means that we need to replace all these `(` and `)` with spaces to make
// sure that we can extract the `p-8`.
//
// However, we also need to make sure that we keep the parens that are part of the
// utility class. E.g.: `bg-(--my-color)`.
b'(' if bracket_stack.is_empty() && !matches!(cursor.prev(), b'-' | b'/') => {
result[cursor.pos] = b' ';
bracket_stack.push(cursor.curr());
}
b'(' | b'[' | b'{' => {
bracket_stack.push(cursor.curr());
}
b')' | b']' | b'}' if !bracket_stack.is_empty() => {
bracket_stack.pop(cursor.curr());
}
// Consume everything else
_ => {}
};
cursor.advance();
}
result
}
}
#[cfg(test)]
mod tests {
use super::Slim;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_slim_pre_processor() {
for (input, expected) in [
// Convert dots to spaces
("div.flex.bg-red-500", "div flex bg-red-500"),
(".flex.bg-red-500", " flex bg-red-500"),
(".bg-red-500.2xl:flex", " bg-red-500 2xl:flex"),
(
".bg-red-500.2xl:flex.bg-green-200.3xl:flex",
" bg-red-500 2xl:flex bg-green-200 3xl:flex",
),
// Keep dots in strings
(r#"div(class="px-2.5")"#, r#"div class="px-2.5")"#),
// Replace top-level `(a-z0-9)[` with `$1 `. E.g.: `.flex[x]` -> `.flex x]`
(".text-xl.text-red-600[", " text-xl text-red-600 "),
// But keep important brackets:
(".text-[#0088cc]", " text-[#0088cc]"),
// Arbitrary value and arbitrary modifier
(
".text-[#0088cc].bg-[#0088cc]/[20%]",
" text-[#0088cc] bg-[#0088cc]/[20%]",
),
// Start of arbitrary property
("[color:red]", "[color:red]"),
// Arbitrary container query
("@[320px]:flex", "@[320px]:flex"),
// Nested brackets
(
"bg-[url(https://example.com/?q=[1,2])]",
"bg-[url(https://example.com/?q=[1,2])]",
),
// Nested brackets, with "invalid" syntax but valid due to nesting
("content-['50[]']", "content-['50[]']"),
// Escaped string
("content-['a\'b\'c\'']", "content-['a\'b\'c\'']"),
// Classes in HTML attributes
("<div id=\"px-2.5\"></div>", "<div id=\"px-2.5\"></div>"),
(
"<div id=\"px-2.5 bg-red-500 2xl:flex bg-green-200 3xl:flex\"></div>",
"<div id=\"px-2.5 bg-red-500 2xl:flex bg-green-200 3xl:flex\"></div>",
),
] {
Slim::test(input, expected);
}
}
#[test]
fn test_nested_slim_syntax() {
let input = r#"
.text-black[
data-controller= ['foo', ('bar' if rand.positive?)].join(' ')
]
.bg-green-300
| BLACK on GREEN - OK
.bg-red-300[
data-foo= 42
]
| Should be BLACK on RED - FAIL
"#;
Slim::test_extract_contains(input, vec!["text-black", "bg-green-300", "bg-red-300"]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/17081
// https://github.com/slim-template/slim?tab=readme-ov-file#verbatim-text-with-trailing-white-space-
#[test]
fn test_single_quotes_to_enforce_trailing_whitespace() {
let input = r#"
div
'A single quote enforces trailing white space
= 1234
.text-red-500.text-3xl
| This text should be red
"#;
let expected = r#"
div
'A single quote enforces trailing white space
= 1234
text-red-500 text-3xl
| This text should be red
"#;
Slim::test(input, expected);
Slim::test_extract_contains(input, vec!["text-red-500", "text-3xl"]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/17277
#[test]
fn test_class_shorthand_followed_by_parens() {
let input = r#"
body.border-t-4.p-8(class="\#{body_classes}" data-hotwire-native="\#{hotwire_native_app?}" data-controller="update-time-zone")
"#;
Slim::test_extract_contains(input, vec!["border-t-4", "p-8"]);
// Additional test with CSS Variable shorthand syntax in the attribute itself because `(`
// and `)` are not valid in the class shorthand version.
//
// Also included an arbitrary value including `(` and `)` to make sure that we don't
// accidentally remove those either.
let input = r#"
body.p-8(class="bg-(--my-color) bg-(--my-color)/(--my-opacity) bg-[url(https://example.com)]")
"#;
Slim::test_extract_contains(
input,
vec![
"p-8",
"bg-(--my-color)",
"bg-(--my-color)/(--my-opacity)",
"bg-[url(https://example.com)]",
],
);
// Top-level class shorthand with parens
let input = r#"
div class="bg-(--my-color) bg-(--my-color)/(--my-opacity)"
"#;
Slim::test_extract_contains(
input,
vec!["bg-(--my-color)", "bg-(--my-color)/(--my-opacity)"],
);
}
#[test]
fn test_strings_only_occur_when_nested() {
let input = r#"
p.mt-2.text-xl
| The quote in the next word, can't be the start of a string
h3.mt-24.text-center.text-4xl.font-bold.italic
| The classes above should be extracted
"#;
Slim::test_extract_contains(
input,
vec![
// First paragraph
"mt-2",
"text-xl",
// second paragraph
"mt-24",
"text-center",
"text-4xl",
"font-bold",
"italic",
],
);
}
#[test]
fn test_arbitrary_code_followed_by_classes() {
let input = r#"
- i < 3
.flex.items-center
"#;
Slim::test_extract_contains(input, vec!["flex", "items-center"]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/17542
#[test]
fn test_embedded_ruby_percent_w_extraction() {
let input = r#"
div[
class=%w[bg-blue-500 w-10 h-10]
]
div[
class=%w[w-10 bg-green-500 h-10]
]
"#;
let expected = "
div \n class= bg-blue-500 w-10 h-10 \n ]
div \n class= w-10 bg-green-500 h-10 \n ]
";
Slim::test(input, expected);
Slim::test_extract_contains(input, vec!["bg-blue-500", "bg-green-500", "w-10", "h-10"]);
}
// https://github.com/tailwindlabs/tailwindcss/issues/20386
#[test]
fn test_embedded_ruby_percent_w_delimiters() {
for (input, expected) in [
// %w<…>, Slim only counts `[({` nesting in attribute values, so the code must be
// wrapped in parentheses to contain spaces
(
"div class=(%w<bg-blue-500 w-10 h-10>)",
"div class= bg-blue-500 w-10 h-10 )",
),
// Nested `<…>` does not end the literal
(
"div class=(%w<flex <nested> px-2.5>)",
"div class= flex <nested> px-2.5 )",
),
// Custom delimiters
("div class=(%w|flex px-2.5|)", "div class= flex px-2.5 )"),
("div class=(%W!flex px-2.5!)", "div class= flex px-2.5 )"),
(
"div class=(%w#text-sm leading-6#)",
"div class= text-sm leading-6 )",
),
(
"div class=(%w=italic tracking-wide=)",
"div class= italic tracking-wide )",
),
// Nested paired delimiters stay balanced inside the literal
(
"div class=(%w[content-['[hello]'] p-4])",
"div class= content-['[hello]'] p-4 )",
),
// Escaped spaces embed a space in a single array element
(
r#"div class=(%w[foo\ bar baz-1])"#,
r#"div class= foo bar baz-1 )"#,
),
// Ruby control line, which is plain Ruby code
("- classes = %w<mt-4 flex>", "- classes = mt-4 flex "),
// A `%` that follows a value is not a percent literal
("| hit rate 50%w.", "| hit rate 50%w "),
] {
Slim::test(input, expected);
}
let input = r#"
div[
class=(%w<bg-blue-500 w-10 h-10>)
]
- classes = %w|w-10 bg-green-500 h-10|
= tag.div class: %W!px-2.5 flex!
"#;
Slim::test_extract_contains(
input,
vec![
"bg-blue-500",
"bg-green-500",
"w-10",
"h-10",
"px-2.5",
"flex",
],
);
}
}

View file

@ -0,0 +1,43 @@
use crate::extractor::pre_processors::pre_processor::PreProcessor;
use bstr::ByteSlice;
#[derive(Debug, Default)]
pub struct Svelte;
impl PreProcessor for Svelte {
fn process(&self, content: &[u8]) -> Vec<u8> {
content
.replace(" class:", " class ")
.replace("\tclass:", " class ")
.replace("\nclass:", " class ")
}
}
#[cfg(test)]
mod tests {
use super::Svelte;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_svelte_pre_processor() {
for (input, expected) in [
// Spaces
(
"<div class:flex class:px-2.5={condition()}>",
"<div class flex class px-2.5={condition()}>",
),
// Tabs
(
"<div\tclass:flex class:px-2.5={condition()}>",
"<div class flex class px-2.5={condition()}>",
),
// Newlines
(
"<div\nclass:flex class:px-2.5={condition()}>",
"<div class flex class px-2.5={condition()}>",
),
] {
Svelte::test(input, expected);
}
}
}

View file

@ -0,0 +1,52 @@
use crate::cursor;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[derive(Debug, Default)]
pub struct TemplateToolkit;
impl PreProcessor for TemplateToolkit {
fn process(&self, content: &[u8]) -> Vec<u8> {
let len = content.len();
let mut result = content.to_vec();
let mut cursor = cursor::Cursor::new(content);
while cursor.pos < len {
match (cursor.curr(), cursor.next()) {
(b'[', b'%') => result[cursor.pos] = b' ',
(b'%', b']') => result[cursor.pos + 1] = b' ',
_ => {}
}
cursor.advance();
}
result
}
}
#[cfg(test)]
mod tests {
use super::TemplateToolkit;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_template_toolkit_pre_processor() {
for (input, expected) in [
(
"[% IF $is_open %]bg-white/40[% ELSE %]bg-white/10[% END %]",
" % IF $is_open % bg-white/40 % ELSE % bg-white/10 % END % ",
),
("[% WRAPPER %]flex[% END %]", " % WRAPPER % flex % END % "),
] {
TemplateToolkit::test(input, expected);
}
}
#[test]
fn test_extraction_between_template_tags_works() {
TemplateToolkit::test_extract_contains(
r#"<div class="[% IF $is_open %]bg-white/40[% ELSE %]bg-white/10[% END %]"></div>"#,
vec!["bg-white/40", "bg-white/10"],
);
}
}

View file

@ -0,0 +1,262 @@
/ https://github.com/tailwindlabs/tailwindcss/pull/17051#issuecomment-2711181352
- star_styles = "size-[800px] mask mask-star bg-gradient-to-r from-secondary via-cyan-400 to-lime-400"
^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^
- crazy_text_styles = "italic font-black bg-gradient-to-r via-60% to-90% from-orange-500 via-secondary to-primary text-transparent bg-clip-text inline-block py-2"
^^^^^^^^^^^^^^^^^ ^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^ ^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^
.relative
^^^^^^^^
- # Blurred background star
.absolute.left-0.z-0{ class: "-top-[400px] -right-[400px]" }
^^^^^^^^ ^^^^^^ ^^^ ^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^^^
.flex.justify-end.blur-3xl
^^^^ ^^^^^^^^^^^ ^^^^^^^^
%div{ class: star_styles }
^^^^^ ^^^^^^^^^^^
.relative.z-10
^^^^^^^^ ^^^^
%h1.mt-8.text-center.text-5xl.font-black.tracking-wide.drop-shadow-lg
^^^^ ^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
%div
Components, Guides, and Paradigms
^^^
.md:inline-flex.md:items-center.justify-center
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
%span.-mr-2
^^^^^
for
^^^
%span.relative.-rotate-3.hover:-rotate-6.hover:scale-125.transition-transform
^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
%span{ class: crazy_text_styles + " absolute blur-xl" }
^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^
&nbsp;CRAZY&nbsp;
%span{ class: crazy_text_styles + " drop-shadow-[0_0_1px_#fff]" }
^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
&nbsp;CRAZY&nbsp;
%span.-ml-3.md:-ml-1
^^^^^ ^^^^^^^^
\-fast Development
%div
in Ruby on Rails!
^^ ^^
.mt-16.text-center
^^^^^ ^^^^^^^^^^^
= daisy_button "😃 Get Started", css: "btn-primary text-xl",
^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^ ^^^^^^^
right_icon: "arrow-right", target: "_blank",
^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^
href: "https://github.com/profoundry-us/loco_motion#locomotion-components"
^^^^ ^^^^^^^^^^^^^^^^^^^^^
.mt-32.lg:flex.lg:space-x-8
^^^^^ ^^^^^^^ ^^^^^^^^^^^^
%div{ class: "md:basis-2/5" }
^^^^^ ^^^^^^^^^^^^
%h3.text-4xl.font-bold
^^^^^^^^ ^^^^^^^^^
Easy, Flexible Components
%p.mt-2.text-xl
^^^^ ^^^^^^^
Powered by the fabulous
^^ ^^^ ^^^^^^^^
= succeed ',' do
^^^^^^^ ^^
= daisy_link("ViewComponent", "https://viewcomponent.org", target: "_blank")
^^^ ^^^^^^
= succeed ', and' do
^^^^^^^ ^^^ ^^
= daisy_link "DaisyUI", "https://daisyui.com/", target: "_blank"
^^^^^^^^^^ ^^^^^^
= daisy_link "TailwindCSS", "https://tailwindcss.com/", target: "_blank"
^^^^^^^^^^ ^^^^^^
libraries, our components are designed to be fast, flexible, and easy to
^^^ ^^^^^^^^^^ ^^^ ^^^^^^^^ ^^ ^^ ^^^ ^^^^ ^^
use <i>directly</i> in Ruby on Rails!
^^^ ^^^^^^^^ ^^ ^^
= doc_example(css: "mt-8 lg:mt-0 lg:basis-3/5 h-44") do
^^^ ^^^^ ^^^^^^^ ^^^^^^^^^^^^ ^^^^ ^^
.flex.flex-col.sm:flex-row.items-center.gap-4
^^^^ ^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^
= daisy_button "Accent Button", css: "btn-accent"
^^^^^^^^^^^^ ^^^ ^^^^^^^^^^
= daisy_tip("Click to Swap") do
^^ ^^
= daisy_swap off: "🌚", on: "🌞", css: "swap-rotate text-4xl"
^^^^^^^^^^ ^^^ ^^ ^^^ ^^^^^^^^^^^ ^^^^^^^^
= daisy_badge "Large Badge", css: "badge-secondary badge-lg"
^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^ ^^^^^^^^
.mt-32.xl:flex.xl:flex-row-reverse.xl:items-center
^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
.text-xl.xl:ml-8
^^^^^^^ ^^^^^^^
%h3.text-4xl.font-bold
^^^^^^^^ ^^^^^^^^^
Simple, Concise Views
%p.mt-2
^^^^
Utilize
= daisy_link("HAML", "https://haml.info/", target: "_blank")
^^^^^^
so your views are simple, concise, and easy to understand.
^^ ^^^^ ^^^^^ ^^^ ^^^ ^^^^ ^^ ^^^^^^^^^^
%p.mt-2
^^^^
No more messy ERB files with all of their closing tags and Ruby wrappers.
^^^^ ^^^^^ ^^^^^ ^^^^ ^^^ ^^ ^^^^^ ^^^^^^^ ^^^^ ^^^ ^^^^^^^^
HAML feels more natural to write and reduces file sizes, making your
^^^^^ ^^^^ ^^^^^^^ ^^ ^^^^^ ^^^ ^^^^^^^ ^^^^ ^^^^^^ ^^^^
views easier to read and maintain.
^^^^^ ^^^^^^ ^^ ^^^^ ^^^ ^^^^^^^^
%p.mt-2
^^^^
:markdown
**PLUS!** You can utilize filters like _Markdown_, CoffeeScript,
^^^ ^^^^^^^ ^^^^^^^ ^^^^
Textile, and many more!
^^^ ^^^^ ^^^^^
.mt-8.xl:mt-0
^^^^ ^^^^^^^
.flex.flex-col.xl:flex.xl:flex-row.w-full
^^^^ ^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^ ^^^^^^
= doc_code(css: "grow", code_css: "xl:pl-2 xl:pr-0 xl:rounded-r-none", language: "erb") do
^^^ ^^^^ ^^^^^^^^ ^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^^^^^^^ ^^^ ^^
:escaped
<% # Ruby %>
<% 5.times do |i| %>
^^^^^ ^^
<% if i.even? %>
^^ ^
<p class="odd">Number <%= i %></p>
^^^^^ ^^^ ^
<% else %>
^^^^
<p class="even">Numero <%= i %></p>
^^^^^ ^^^^ ^
<% end %>
^^^
<% end %>
^^^
= doc_code(css: "grow mt-8 xl:mt-0", pre_css: "xl:h-full", code_css: "xl:pl-0 xl:pr-2 xl:h-full xl:rounded-l-none") do
^^^ ^^^^ ^^^^ ^^^^^^^ ^^^^^^^ ^^^^^^^^^ ^^^^^^^^ ^^^^^^^ ^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^^^^^ ^^
:escaped
- # HAML
- 5.times do |i|
^^^^^ ^^
- if i.even?
^^
%p.odd Number \#{i}
^^^ ^
- else
^^^^
%p.even Numero \#{i}
^^^^ ^
%h3.mt-32.text-4xl.font-bold
^^^^^ ^^^^^^^^ ^^^^^^^^^
Build Your <i>OWN</i> Components
%p.mt-2.text-xl
^^^^ ^^^^^^^
Can't find exactly what you need? No problem! Build your own components
^ ^^^^ ^^^^^^^ ^^^^ ^^^ ^^^^^^^^ ^^^^ ^^^ ^^^^^^^^^^
with ease using our simple, flexible, and powerful DSL.
^^^^ ^^^^ ^^^^^ ^^^ ^^^ ^^^^^^^^
.mt-8.xl:flex.xl:space-x-8
^^^^ ^^^^^^^ ^^^^^^^^^^^^
%div
= doc_code(language: "ruby") do
^^^^^^^^ ^^^^ ^^
:escaped
# app/components/application_component.rb
^^
class ApplicationComponent < LocoMotion::BaseComponent
^^^^^
# Add your custom / shared component logic here!
^^^^ ^^^^^^ ^^^^^^ ^^^^^^^^^ ^^^^^ ^^^^^
end
^^^
= doc_code(language: "haml", css: "mt-8") do
^^^^^^^^ ^^^^ ^^^ ^^^^ ^^
:escaped
- # app/components/character_component.html.haml
= part(:component) do
^^
= part(:head)
= part(:body) do
^^
= content
^^^^^^^
= part(:legs)
%div.mt-8.xl:mt-0
^^^^ ^^^^^^^
= doc_code(language: "ruby") do
^^^^^^^^ ^^^^ ^^
:escaped
# app/components/character_component.rb
^^
class CharacterComponent < ApplicationComponent
^^^^^
define_parts :head, :body, :legs
^^^^^^^^^^^^
def before_render
^^^ ^^^^^^^^^^^^^
set_tag_name(:head, :h1)
^^^^^^^^^^^^
add_css(:head, "text-3xl font-bold")
^^^^^^^ ^^^^^^^^ ^^^^^^^^^
set_tag_name(:body, :p)
^^^^^^^^^^^^
add_stimulus_controller(:body, "character-body")
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
add_css(:body, "text-lg")
^^^^^^^ ^^^^^^^
set_tag_name(:legs, :footer)
^^^^^^^^^^^^
add_css(:legs, "text-sm")
^^^^^^^ ^^^^^^^
end
^^^
end
^^^
%h3.mt-24.text-center.text-4xl.font-bold.italic
^^^^^ ^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^^ ^^^^^^
More Coming Soon!
%p.mt-2.text-xl.text-center
^^^^ ^^^^^^^ ^^^^^^^^^^^
Keen an eye out as we'll be adding more components, guides, and
^^ ^^^ ^^^ ^^ ^^ ^^ ^^ ^^^^^^ ^^^^ ^^^
%br.max-sm:hidden
^^^^^^^^^^^^^
suggested gems for you to build amazing Rails apps!
^^^^^^^^^ ^^^^ ^^^ ^^^ ^^ ^^^^^ ^^^^^^^ ^^^^^
.mt-4.text-center
^^^^ ^^^^^^^^^^^
= daisy_button "😉 Get Started", css: "btn-primary text-xl",
^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^ ^^^^^^^
right_icon: "arrow-right", target: "_blank",
^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^
class: "px-2.5"
^^^^^ ^^^^^^
href: "https://github.com/profoundry-us/loco_motion#locomotion-components"
^^^^ ^^^^^^^^^^^^^^^^^^^^^

View file

@ -0,0 +1,30 @@
:ruby
- devices_classes = 'size-5 mr-2px'
^^^^^^^^^^^^^^^ ^^^^^^ ^^^^^^
- icon_classes = 'w-[12px] h-[12px]'
^^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^
!!!
%html{ lang: 'en' }
^^^^ ^^
%head
%title Tailwind v4.1.4 + HAML bug
^^^^^^ ^^^
-# This is a comment
^^ ^ ^^^^^^^
A multi-line comment
^^^^^^^^^^ ^^^^^^^
With more indentation
^^^^ ^^^^^^^^^^^
Which can dedent again just fine
^^^ ^^^^^^ ^^^^^ ^^^^ ^^^^
%body
- icon_classes = 'self-center w-[16px] h-[16px]'
^^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^
.flex{ class: icon_classes }
^^^^ ^^^^^ ^^^^^^^^^^^^
.flex{ class: devices_classes }
^^^^ ^^^^^ ^^^^^^^^^^^^^^^

View file

@ -0,0 +1,155 @@
/ https://github.com/tailwindlabs/tailwindcss/pull/17051#issuecomment-2711181352
- star_styles = "size-[800px] mask mask-star bg-gradient-to-r from-secondary via-cyan-400 to-lime-400"
- crazy_text_styles = "italic font-black bg-gradient-to-r via-60% to-90% from-orange-500 via-secondary to-primary text-transparent bg-clip-text inline-block py-2"
.relative
- # Blurred background star
.absolute.left-0.z-0{ class: "-top-[400px] -right-[400px]" }
.flex.justify-end.blur-3xl
%div{ class: star_styles }
.relative.z-10
%h1.mt-8.text-center.text-5xl.font-black.tracking-wide.drop-shadow-lg
%div
Components, Guides, and Paradigms
.md:inline-flex.md:items-center.justify-center
%span.-mr-2
for
%span.relative.-rotate-3.hover:-rotate-6.hover:scale-125.transition-transform
%span{ class: crazy_text_styles + " absolute blur-xl" }
&nbsp;CRAZY&nbsp;
%span{ class: crazy_text_styles + " drop-shadow-[0_0_1px_#fff]" }
&nbsp;CRAZY&nbsp;
%span.-ml-3.md:-ml-1
\-fast Development
%div
in Ruby on Rails!
.mt-16.text-center
= daisy_button "😃 Get Started", css: "btn-primary text-xl",
right_icon: "arrow-right", target: "_blank",
href: "https://github.com/profoundry-us/loco_motion#locomotion-components"
.mt-32.lg:flex.lg:space-x-8
%div{ class: "md:basis-2/5" }
%h3.text-4xl.font-bold
Easy, Flexible Components
%p.mt-2.text-xl
Powered by the fabulous
= succeed ',' do
= daisy_link("ViewComponent", "https://viewcomponent.org", target: "_blank")
= succeed ', and' do
= daisy_link "DaisyUI", "https://daisyui.com/", target: "_blank"
= daisy_link "TailwindCSS", "https://tailwindcss.com/", target: "_blank"
libraries, our components are designed to be fast, flexible, and easy to
use <i>directly</i> in Ruby on Rails!
= doc_example(css: "mt-8 lg:mt-0 lg:basis-3/5 h-44") do
.flex.flex-col.sm:flex-row.items-center.gap-4
= daisy_button "Accent Button", css: "btn-accent"
= daisy_tip("Click to Swap") do
= daisy_swap off: "🌚", on: "🌞", css: "swap-rotate text-4xl"
= daisy_badge "Large Badge", css: "badge-secondary badge-lg"
.mt-32.xl:flex.xl:flex-row-reverse.xl:items-center
.text-xl.xl:ml-8
%h3.text-4xl.font-bold
Simple, Concise Views
%p.mt-2
Utilize
= daisy_link("HAML", "https://haml.info/", target: "_blank")
so your views are simple, concise, and easy to understand.
%p.mt-2
No more messy ERB files with all of their closing tags and Ruby wrappers.
HAML feels more natural to write and reduces file sizes, making your
views easier to read and maintain.
%p.mt-2
:markdown
**PLUS!** You can utilize filters like _Markdown_, CoffeeScript,
Textile, and many more!
.mt-8.xl:mt-0
.flex.flex-col.xl:flex.xl:flex-row.w-full
= doc_code(css: "grow", code_css: "xl:pl-2 xl:pr-0 xl:rounded-r-none", language: "erb") do
:escaped
<% # Ruby %>
<% 5.times do |i| %>
<% if i.even? %>
<p class="odd">Number <%= i %></p>
<% else %>
<p class="even">Numero <%= i %></p>
<% end %>
<% end %>
= doc_code(css: "grow mt-8 xl:mt-0", pre_css: "xl:h-full", code_css: "xl:pl-0 xl:pr-2 xl:h-full xl:rounded-l-none") do
:escaped
- # HAML
- 5.times do |i|
- if i.even?
%p.odd Number \#{i}
- else
%p.even Numero \#{i}
%h3.mt-32.text-4xl.font-bold
Build Your <i>OWN</i> Components
%p.mt-2.text-xl
Can't find exactly what you need? No problem! Build your own components
with ease using our simple, flexible, and powerful DSL.
.mt-8.xl:flex.xl:space-x-8
%div
= doc_code(language: "ruby") do
:escaped
# app/components/application_component.rb
class ApplicationComponent < LocoMotion::BaseComponent
# Add your custom / shared component logic here!
end
= doc_code(language: "haml", css: "mt-8") do
:escaped
- # app/components/character_component.html.haml
= part(:component) do
= part(:head)
= part(:body) do
= content
= part(:legs)
%div.mt-8.xl:mt-0
= doc_code(language: "ruby") do
:escaped
# app/components/character_component.rb
class CharacterComponent < ApplicationComponent
define_parts :head, :body, :legs
def before_render
set_tag_name(:head, :h1)
add_css(:head, "text-3xl font-bold")
set_tag_name(:body, :p)
add_stimulus_controller(:body, "character-body")
add_css(:body, "text-lg")
set_tag_name(:legs, :footer)
add_css(:legs, "text-sm")
end
end
%h3.mt-24.text-center.text-4xl.font-bold.italic
More Coming Soon!
%p.mt-2.text-xl.text-center
Keen an eye out as we'll be adding more components, guides, and
%br.max-sm:hidden
suggested gems for you to build amazing Rails apps!
.mt-4.text-center
= daisy_button "😉 Get Started", css: "btn-primary text-xl",
right_icon: "arrow-right", target: "_blank",
class: "px-2.5"
href: "https://github.com/profoundry-us/loco_motion#locomotion-components"

View file

@ -0,0 +1,19 @@
:ruby
- devices_classes = 'size-5 mr-2px'
- icon_classes = 'w-[12px] h-[12px]'
!!!
%html{ lang: 'en' }
%head
%title Tailwind v4.1.4 + HAML bug
-# This is a comment
A multi-line comment
With more indentation
Which can dedent again just fine
%body
- icon_classes = 'self-center w-[16px] h-[16px]'
.flex{ class: icon_classes }
.flex{ class: devices_classes }

View file

@ -0,0 +1,179 @@
use crate::cursor;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[derive(Debug, Default)]
pub struct Twig;
impl PreProcessor for Twig {
fn process(&self, content: &[u8]) -> Vec<u8> {
let len = content.len();
let mut result = content.to_vec();
let mut cursor = cursor::Cursor::new(content);
let mut bracket_stack = vec![];
const ADD_CLASS: &[u8] = b"addClass";
const REMOVE_CLASS: &[u8] = b"removeClass";
while cursor.pos < len {
match (!bracket_stack.is_empty(), cursor.curr()) {
// addClass(
// ^
(false, b'(')
if cursor.pos >= ADD_CLASS.len()
&& matches!(
&content[cursor.pos - ADD_CLASS.len()..cursor.pos],
ADD_CLASS
) =>
{
bracket_stack.push(cursor.curr());
result[cursor.pos] = b' ';
}
// removeClass(
// ^
(false, b'(')
if cursor.pos >= REMOVE_CLASS.len()
&& matches!(
&content[cursor.pos - REMOVE_CLASS.len()..cursor.pos],
REMOVE_CLASS
) =>
{
bracket_stack.push(cursor.curr());
result[cursor.pos] = b' ';
}
(true, b'(') => {
bracket_stack.push(cursor.curr());
}
(true, b')') => {
bracket_stack.pop();
if bracket_stack.is_empty() {
result[cursor.pos] = b' ';
}
}
_ => {}
}
cursor.advance();
}
result
}
}
#[cfg(test)]
mod tests {
use super::Twig;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_twig_pre_processor() {
for (input, expected) in [
(
// Ensure we don't crash when we encounter an `(`
"(p-(--value))",
"(p-(--value))",
),
(
// addClass with single argument
"addClass(p-(--value))",
"addClass p-(--value) ",
),
(
// addClass with single argument
"addClass(m-4 p-8 w-full)",
"addClass m-4 p-8 w-full ",
),
(
// removeClass with single argument
"removeClass(p-(--value))",
"removeClass p-(--value) ",
),
(
// removeClass with single argument
"removeClass(m-4 p-8 w-full)",
"removeClass m-4 p-8 w-full ",
),
(
// Combined with single arguments
"addClass(m-(--value))|removeClass(p-(--value))",
"addClass m-(--value) |removeClass p-(--value) ",
),
] {
Twig::test(input, expected);
}
}
// https://github.com/tailwindlabs/tailwindcss/issues/19458
#[test]
fn test_extraction_in_add_class_and_remove_class_works() {
// Inside normal HTML
let input = r#"
<div data-loading="addClass(opacity-50)">
<!-- -->
</div>
"#;
let expected = r#"
<div data-loading="addClass opacity-50 ">
<!-- -->
</div>
"#;
Twig::test(input, expected);
Twig::test_extract_contains(input, vec!["opacity-50"]);
// Inside a component
let input = r#"
<twig:d:Card
{{ ...attributes.defaults({
as: "a",
class: "cursor-pointer bg-base-200 card-sm md:card-md lg:card-lg",
role: "button",
tabindex: "0",
"data-action": "live#$render",
"data-loading": "addAttribute(disabled)",
"data-poll": "delay(60000)|$render",
"data-loading": "addClass(border border-red-500 opacity-75)",
})
}}
>
"#;
let expected = r#"
<twig:d:Card
{{ ...attributes.defaults({
as: "a",
class: "cursor-pointer bg-base-200 card-sm md:card-md lg:card-lg",
role: "button",
tabindex: "0",
"data-action": "live#$render",
"data-loading": "addAttribute(disabled)",
"data-poll": "delay(60000)|$render",
"data-loading": "addClass border border-red-500 opacity-75 ",
})
}}
>
"#;
Twig::test(input, expected);
Twig::test_extract_contains(
input,
vec![
// class
"cursor-pointer",
"bg-base-200",
"card-sm",
"md:card-md",
"lg:card-lg",
// data-loading
"border",
"border-red-500",
"opacity-75",
],
);
}
}

View file

@ -0,0 +1,60 @@
use crate::extractor::pre_processors::pre_processor::PreProcessor;
use crate::scanner::pre_process_input;
use bstr::ByteSlice;
use regex::Regex;
use std::sync;
static TEMPLATE_REGEX: sync::LazyLock<Regex> = sync::LazyLock::new(|| {
Regex::new(r#"<template lang=['"]([^"']*)['"]>([\s\S]*)<\/template>"#).unwrap()
});
#[derive(Debug, Default)]
pub struct Vue;
impl PreProcessor for Vue {
fn process(&self, content: &[u8]) -> Vec<u8> {
let mut result = content.to_vec();
// Only process template tags if content is valid UTF-8
if let Ok(content_as_str) = std::str::from_utf8(content) {
for (_, [lang, body]) in TEMPLATE_REGEX
.captures_iter(content_as_str)
.map(|c| c.extract())
{
let replaced = pre_process_input(body.as_bytes().to_vec(), lang);
result = result.replace(body, replaced);
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::Vue;
use crate::extractor::pre_processors::pre_processor::PreProcessor;
#[test]
fn test_vue_template_pug() {
let input = r#"
<template lang="pug">
.bg-neutral-900.text-red-500 This is a test.
</template>
"#;
Vue::test_extract_contains(input, vec!["bg-neutral-900", "text-red-500"]);
}
#[test]
fn test_invalid_utf8_does_not_panic() {
// Invalid UTF-8 sequence: 0x80 is a continuation byte without a leading byte
let invalid_utf8: &[u8] = &[0x80, 0x81, 0x82];
let processor = Vue::default();
// Should not panic, just return the input unchanged
let result = processor.process(invalid_utf8);
assert_eq!(result, invalid_utf8);
}
}

View file

@ -0,0 +1,138 @@
use crate::cursor;
use crate::extractor::machine::{Machine, MachineState};
use classification_macros::ClassifyBytes;
/// Extracts a string (including the quotes) from the input.
///
/// Rules:
///
/// - The string must start and end with the same quote character.
/// - The string cannot contain any whitespace characters.
/// - The string can contain any other character except for the quote character (unless it's escaped).
/// - Balancing of brackets is not required.
///
///
/// E.g.:
///
/// ```text
/// 'hello_world'
/// ^^^^^^^^^^^^^
///
/// content-['hello_world']
/// ^^^^^^^^^^^^^
/// ```
#[derive(Debug, Default)]
pub struct StringMachine;
impl Machine for StringMachine {
#[inline(always)]
fn reset(&mut self) {}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
if Class::Quote != cursor.curr().into() {
return MachineState::Idle;
}
// Start of a string
let len = cursor.input.len();
let start_pos = cursor.pos;
let end_char = cursor.curr();
cursor.advance();
while cursor.pos < len {
match cursor.curr().into() {
Class::Escape => match cursor.next().into() {
// An escaped whitespace character is not allowed
Class::Whitespace => return MachineState::Idle,
// An escaped character, skip ahead to the next character
_ => cursor.advance(),
},
// End of the string
Class::Quote if cursor.curr() == end_char => return self.done(start_pos, cursor),
// Any kind of whitespace is not allowed
Class::Whitespace => return MachineState::Idle,
// Everything else is valid
_ => {}
};
cursor.advance()
}
MachineState::Idle
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ClassifyBytes)]
enum Class {
#[bytes(b'"', b'\'', b'`')]
Quote,
#[bytes(b'\\')]
Escape,
#[bytes(b' ', b'\t', b'\n', b'\r', b'\x0C')]
Whitespace,
#[fallback]
Other,
}
#[cfg(test)]
mod tests {
use super::StringMachine;
use crate::extractor::machine::Machine;
use pretty_assertions::assert_eq;
#[test]
#[ignore]
fn test_string_machine_performance() {
let input = r#"There will be a 'string' in this input, even "strings_with_other_quotes_and_\#escaped_characters" "#.repeat(100);
StringMachine::test_throughput(100_000, &input);
StringMachine::test_duration_once(&input);
todo!()
}
#[test]
fn test_string_machine_extraction() {
for (input, expected) in [
// Simple string
("'foo'", vec!["'foo'"]),
// String as part of a candidate
("content-['hello_world']", vec!["'hello_world'"]),
// With nested quotes
(r#"'"`hello`"'"#, vec![r#"'"`hello`"'"#]),
// With escaped opening quote
(r#"'Tailwind\'s_parser'"#, vec![r#"'Tailwind\'s_parser'"#]),
(
r#"'Tailwind\'\'s_parser'"#,
vec![r#"'Tailwind\'\'s_parser'"#],
),
(
r#"'Tailwind\'\'\'s_parser'"#,
vec![r#"'Tailwind\'\'\'s_parser'"#],
),
(
r#"'Tailwind\'\'\'\'s_parser'"#,
vec![r#"'Tailwind\'\'\'\'s_parser'"#],
),
// Spaces are not allowed
("' hello world '", vec![]),
// With unfinished quote
("'unfinished_quote", vec![]),
// An escape at the end will never be valid, because it _must_ be followed by the
// ending quote.
(r#"'escaped_ending_quote\'"#, vec![]),
(r#"'escaped_end\"#, vec![]),
] {
assert_eq!(StringMachine::test_extract_all(input), expected);
}
}
}

View file

@ -0,0 +1,346 @@
use crate::cursor;
use crate::extractor::arbitrary_property_machine::ArbitraryPropertyMachine;
use crate::extractor::machine::{Machine, MachineState};
use crate::extractor::modifier_machine::ModifierMachine;
use crate::extractor::named_utility_machine::NamedUtilityMachine;
use classification_macros::ClassifyBytes;
#[derive(Debug, Default)]
pub struct UtilityMachine {
/// Start position of the utility
start_pos: usize,
/// Whether the legacy important marker `!` was used
legacy_important: bool,
arbitrary_property_machine: ArbitraryPropertyMachine,
named_utility_machine: NamedUtilityMachine,
modifier_machine: ModifierMachine,
}
impl Machine for UtilityMachine {
#[inline(always)]
fn reset(&mut self) {
self.start_pos = 0;
self.legacy_important = false;
}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match cursor.curr().into() {
// LEGACY: Important marker
Class::Exclamation => {
self.legacy_important = true;
match cursor.next().into() {
// Start of an arbitrary property
//
// E.g.: `![color:red]`
// ^
Class::OpenBracket => {
self.start_pos = cursor.pos;
cursor.advance();
self.parse_arbitrary_property(cursor)
}
// Start of a named utility
//
// E.g.: `!flex`
// ^
_ => {
self.start_pos = cursor.pos;
cursor.advance();
self.parse_named_utility(cursor)
}
}
}
// Start of an arbitrary property
//
// E.g.: `[color:red]`
// ^
Class::OpenBracket => {
self.start_pos = cursor.pos;
self.parse_arbitrary_property(cursor)
}
// Everything else might be a named utility. Delegate to the named utility machine
// to determine if it's a named utility or not.
_ => {
self.start_pos = cursor.pos;
self.parse_named_utility(cursor)
}
}
}
}
impl UtilityMachine {
fn parse_arbitrary_property(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match self.arbitrary_property_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => match cursor.next().into() {
// End of arbitrary property, but there is a potential modifier.
//
// E.g.: `[color:#0088cc]/`
// ^
Class::Slash => {
cursor.advance();
self.parse_modifier(cursor)
}
// End of arbitrary property, but there is an `!`.
//
// E.g.: `[color:#0088cc]!`
// ^
Class::Exclamation => {
cursor.advance();
self.parse_important(cursor)
}
// End of arbitrary property
//
// E.g.: `[color:#0088cc]`
// ^
_ => self.done(self.start_pos, cursor),
},
}
}
fn parse_named_utility(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match self.named_utility_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => match cursor.next().into() {
// End of a named utility, but there is a potential modifier.
//
// E.g.: `bg-red-500/`
// ^
Class::Slash => {
cursor.advance();
self.parse_modifier(cursor)
}
// End of named utility, but there is an `!`.
//
// E.g.: `bg-red-500!`
// ^
Class::Exclamation => {
cursor.advance();
self.parse_important(cursor)
}
// End of a named utility
//
// E.g.: `bg-red-500`
// ^
_ => self.done(self.start_pos, cursor),
},
}
}
fn parse_modifier(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match self.modifier_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => match cursor.next().into() {
// A modifier followed by a modifier is invalid
Class::Slash => self.restart(),
// A modifier followed by the important marker `!`
Class::Exclamation => {
cursor.advance();
self.parse_important(cursor)
}
// Everything else is valid
_ => self.done(self.start_pos, cursor),
},
}
}
fn parse_important(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
// Only the `!` is valid if we didn't start with `!`
//
// E.g.:
//
// ```
// !bg-red-500!
// ^ invalid because of the first `!`
// ```
if self.legacy_important {
return self.restart();
}
self.done(self.start_pos, cursor)
}
}
#[derive(Debug, Clone, Copy, ClassifyBytes)]
enum Class {
#[bytes(b'!')]
Exclamation,
#[bytes(b'[')]
OpenBracket,
#[bytes(b'/')]
Slash,
#[fallback]
Other,
}
#[cfg(test)]
mod tests {
use super::UtilityMachine;
use crate::extractor::machine::Machine;
use pretty_assertions::assert_eq;
#[test]
#[ignore]
fn test_utility_machine_performance() {
let input = r#"<button type="button" class="absolute -top-1 -left-1.5 flex items-center justify-center p-1.5 text-gray-400">"#.repeat(100);
UtilityMachine::test_throughput(100_000, &input);
UtilityMachine::test_duration_once(&input);
todo!()
}
#[test]
fn test_utility_extraction() {
for (input, expected) in [
// Simple utility
("flex", vec!["flex"]),
// Simple utility with special character(s)
("@container", vec!["@container"]),
// Single character utility
("a", vec!["a"]),
// Important utilities
("!flex", vec!["!flex"]),
("flex!", vec!["flex!"]),
("flex! block", vec!["flex!", "block"]),
// With dashes
("items-center", vec!["items-center"]),
("items--center", vec!["items--center"]),
// Inside a string
("'flex'", vec!["flex"]),
// Multiple utilities
("flex items-center", vec!["flex", "items-center"]),
// Arbitrary property
("[color:red]", vec!["[color:red]"]),
("![color:red]", vec!["![color:red]"]),
("[color:red]!", vec!["[color:red]!"]),
("[color:red]/20", vec!["[color:red]/20"]),
("![color:red]/20", vec!["![color:red]/20"]),
("[color:red]/20!", vec!["[color:red]/20!"]),
// Modifiers
("bg-red-500/20", vec!["bg-red-500/20"]),
("bg-red-500/[20%]", vec!["bg-red-500/[20%]"]),
(
"bg-red-500/(--my-opacity)",
vec!["bg-red-500/(--my-opacity)"],
),
// Modifiers with important (legacy)
("!bg-red-500/20", vec!["!bg-red-500/20"]),
("!bg-red-500/[20%]", vec!["!bg-red-500/[20%]"]),
(
"!bg-red-500/(--my-opacity)",
vec!["!bg-red-500/(--my-opacity)"],
),
// Modifiers with important
("bg-red-500/20!", vec!["bg-red-500/20!"]),
("bg-red-500/[20%]!", vec!["bg-red-500/[20%]!"]),
(
"bg-red-500/(--my-opacity)!",
vec!["bg-red-500/(--my-opacity)!"],
),
// Arbitrary value with bracket notation
("bg-[#0088cc]", vec!["bg-[#0088cc]"]),
// Arbitrary value with arbitrary property shorthand modifier
(
"bg-[#0088cc]/(--my-opacity)",
vec!["bg-[#0088cc]/(--my-opacity)"],
),
// Arbitrary value with CSS property shorthand
("bg-(--my-color)", vec!["bg-(--my-color)"]),
// Multiple utilities including arbitrary property shorthand
(
"bg-(--my-color) flex px-(--my-padding)",
vec!["bg-(--my-color)", "flex", "px-(--my-padding)"],
),
// --------------------------------------------------------
// Exceptions:
("bg-red-500/20/20", vec![]),
("bg-[#0088cc]/20/20", vec![]),
] {
for (wrapper, additional) in [
// No wrapper
("{}", vec![]),
// With leading spaces
(" {}", vec![]),
// With trailing spaces
("{} ", vec![]),
// Surrounded by spaces
(" {} ", vec![]),
// Inside a string
("'{}'", vec![]),
// Inside a function call
("fn('{}')", vec![]),
// Inside nested function calls
("fn1(fn2('{}'))", vec!["fn1", "fn2"]),
// --------------------------
//
// HTML
// Inside a class (on its own)
(r#"<div class="{}"></div>"#, vec!["div", "class"]),
// Inside a class (first)
(r#"<div class="{} foo"></div>"#, vec!["div", "class", "foo"]),
// Inside a class (second)
(r#"<div class="foo {}"></div>"#, vec!["div", "class", "foo"]),
// Inside a class (surrounded)
(
r#"<div class="foo {} bar"></div>"#,
vec!["div", "class", "foo", "bar"],
),
// --------------------------
//
// JavaScript
// Inside a variable
(r#"let classes = '{}';"#, vec!["let", "classes"]),
// Inside an object (key)
(
r#"let classes = { '{}': true };"#,
vec!["let", "classes", "true"],
),
// Inside an object (no spaces, key)
(r#"let classes = {'{}':true};"#, vec!["let", "classes"]),
// Inside an object (value)
(
r#"let classes = { primary: '{}' };"#,
vec!["let", "classes", "primary"],
),
// Inside an object (no spaces, value)
(
r#"let classes = {primary:'{}'};"#,
vec!["let", "classes", "primary"],
),
// Inside an array
(r#"let classes = ['{}'];"#, vec!["let", "classes"]),
] {
let input = wrapper.replace("{}", input);
let mut expected = expected.clone();
expected.extend(additional);
expected.sort();
let mut actual = UtilityMachine::test_extract_all(&input);
actual.sort();
if actual != expected {
dbg!(&input);
}
assert_eq!(actual, expected);
}
}
}
}

View file

@ -0,0 +1,146 @@
use crate::cursor;
use crate::extractor::arbitrary_value_machine::ArbitraryValueMachine;
use crate::extractor::machine::{Machine, MachineState};
use crate::extractor::named_variant_machine::NamedVariantMachine;
use classification_macros::ClassifyBytes;
#[derive(Debug, Default)]
pub struct VariantMachine {
arbitrary_value_machine: ArbitraryValueMachine,
named_variant_machine: NamedVariantMachine,
}
impl Machine for VariantMachine {
#[inline(always)]
fn reset(&mut self) {}
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match cursor.curr().into() {
// Start of an arbitrary variant
//
// E.g.: `[&:hover]:`
// ^
Class::OpenBracket => {
let start_pos = cursor.pos;
match self.arbitrary_value_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => self.parse_arbitrary_end(start_pos, cursor),
}
}
// Start of a named variant
_ => {
let start_pos = cursor.pos;
match self.named_variant_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => self.done(start_pos, cursor),
}
}
}
}
}
impl VariantMachine {
#[inline(always)]
fn parse_arbitrary_end(
&mut self,
start_pos: usize,
cursor: &mut cursor::Cursor<'_>,
) -> MachineState {
match cursor.next().into() {
// End of an arbitrary value, must be followed by a `:`
//
// E.g.: `[&:hover]:`
// ^
Class::Colon => {
cursor.advance();
self.done(start_pos, cursor)
}
// Everything else is invalid
_ => self.restart(),
}
}
}
#[derive(Debug, Clone, Copy, ClassifyBytes)]
enum Class {
#[bytes(b'[')]
OpenBracket,
#[bytes(b':')]
Colon,
#[fallback]
Other,
}
#[cfg(test)]
mod tests {
use super::VariantMachine;
use crate::extractor::machine::Machine;
use pretty_assertions::assert_eq;
#[test]
#[ignore]
fn test_variant_machine_performance() {
let input = r#"<button class="hover:focus:flex data-[state=pending]:[&.in-progress]:flex supports-(--my-variable):flex group-hover/named:not-has-peer-data-disabled:flex">"#;
VariantMachine::test_throughput(100_000, input);
VariantMachine::test_duration_once(input);
todo!()
}
#[test]
fn test_variant_extraction() {
for (input, expected) in [
// Simple variant
("hover:flex", vec!["hover:"]),
// Single character variant
("a:flex", vec!["a:"]),
("*:flex", vec!["*:"]),
// With special characters
("**:flex", vec!["**:"]),
// With dashes
("data-disabled:flex", vec!["data-disabled:"]),
// Multiple variants
("hover:focus:flex", vec!["hover:", "focus:"]),
// Arbitrary variant
("[&:hover:focus]:flex", vec!["[&:hover:focus]:"]),
// Arbitrary variant with nested brackets
(
"[&>[data-slot=icon]:last-child]:",
vec!["[&>[data-slot=icon]:last-child]:"],
),
(
"sm:[&>[data-slot=icon]:last-child]:",
vec!["sm:", "[&>[data-slot=icon]:last-child]:"],
),
(
"[:is(italic):is(underline)]:",
vec!["[:is(italic):is(underline)]:"],
),
// Modifiers
("group-hover/foo:flex", vec!["group-hover/foo:"]),
("group-hover/[.parent]:flex", vec!["group-hover/[.parent]:"]),
// Arbitrary variant with bracket notation
("data-[state=pending]:flex", vec!["data-[state=pending]:"]),
// Arbitrary variant with CSS property shorthand
("supports-(--my-color):flex", vec!["supports-(--my-color):"]),
// -------------------------------------------------------------
// Exceptions
// Empty arbitrary variant is not allowed
("[]:flex", vec![]),
// Named variant must be followed by `:`
("hover", vec![]),
// Modifier cannot be followed by another modifier. However, we don't check boundary
// characters in this state machine so we will get `bar:`.
("group-hover/foo/bar:flex", vec!["bar:"]),
] {
assert_eq!(VariantMachine::test_extract_all(input), expected);
}
}
}

View file

@ -10,7 +10,7 @@ pub fn fast_skip(cursor: &Cursor) -> Option<usize> {
return None;
}
if !cursor.curr.is_ascii_whitespace() {
if !cursor.curr().is_ascii_whitespace() {
return None;
}

View file

@ -0,0 +1,552 @@
<div class="font-semibold px-3 text-left text-gray-900 py-3.5 text-sm">
<nav class="font-medium text-gray-900">
<ul class="h-7 justify-center rounded-full items-center w-7 flex mx-auto">
<li class="h-0.5 inset-x-0 absolute bottom-0">
<a href="#" target="_blank" class="space-y-1 px-2 mt-3">
This is link 4132f37a-a03f-4776-9a8e-1b70ff626f71
</a>
<img
class="text-gray-900 font-medium text-sm ml-3.5"
alt="Profile picture of user 40faf8f0-6221-4ec4-a65e-fe22e1d9abd2"
src="https://example.org/pictures/4f7d7f80-e9cd-447a-9b35-f9d93befe025"
/>
</li>
<li class="text-indigo-700 order-1 font-semibold">
<ol class="h-24 sm:w-32 w-24 object-center rounded-md sm:h-32 object-cover">
<li class="lg:justify-center lg:gap-x-12 hidden lg:flex lg:min-w-0 lg:flex-1">
<img
class="hover:bg-gray-100 bg-gray-50 py-1.5 focus:z-10 text-gray-400"
alt="Profile picture of user d27b5a21-1622-4f3a-ba7d-6230fae487c2"
src="https://example.org/pictures/2a026d06-0e67-467d-babf-8a18614667f2"
/>
<ul class="w-6 h-6 mr-3 flex-shrink-0">
<li class="flow-root">
<img
class="bg-white focus:ring-indigo-500 group font-medium items-center inline-flex focus:ring-offset-2 focus:ring-2 focus:outline-none text-base rounded-md hover:text-gray-900"
alt="Profile picture of user 0052230e-90d8-4d84-ab64-904b87fe4622"
src="https://example.org/pictures/f7ec91ba-17a7-470f-a472-705a8b5a79ce"
/>
</li>
<li class="items-center right-0 flex pointer-events-none absolute inset-y-0">
<img
class="ml-3"
alt="Profile picture of user a7c51adf-3917-4e41-b5f1-d263768c9adf"
src="https://example.org/pictures/a56323a8-8aa3-4d39-8c79-9ee875ecd6f0"
/>
</li>
</ul>
<ul class="hover:bg-opacity-75 hover:bg-indigo-500 text-white">
<li class="font-medium hover:text-indigo-500 text-indigo-600 text-sm">
<ul class="sr-only"></ul>
<ol class="flex-col px-8 flex pt-8"></ol>
<a href="#" class="items-center sm:items-start flex">
This is link 8c59d1ad-9ef0-4a41-ab15-e9fe04d77ae2
</a>
</li>
<li class="rounded-full w-8 h-8">
<ol class="lg:grid lg:grid-cols-12"></ol>
<a
href="#"
target="_blank"
rel="noreferrer"
class="px-4 border-t py-6 space-y-6 border-gray-200"
>
This is link 4182f198-ff54-45a4-ace9-e6f46a60ec92
</a>
</li>
<li class="border-gray-700 border-t pt-4 pb-3">
<ol class="font-medium text-gray-900 p-2 block -m-2"></ol>
<ol
class="sm:py-24 to-green-400 lg:px-0 bg-gradient-to-r lg:items-center lg:justify-end sm:px-6 lg:bg-none from-cyan-600 px-4 lg:pl-8 py-16 lg:flex"
></ol>
<img
class="lg:gap-24 lg:grid-cols-2 lg:grid lg:mx-auto lg:items-start lg:max-w-7xl lg:px-8"
alt="Profile picture of user c44d18a8-a1f1-4bf4-87b0-89f37e34aba1"
src="https://example.org/pictures/527dca2c-5afe-4a5c-96cd-706396701c36"
/>
</li>
<li class="hover:bg-gray-100 bg-white focus:z-10 text-gray-900 relative py-1.5">
<a
href="#"
target="_blank"
rel="noreferrer"
class="text-sm font-medium text-gray-500"
>
This is link f6db0c8d-6409-4abd-9af1-d3e68ebbd25c
</a>
<a href="#" rel="noreferrer" class="text-sm font-medium mt-12">
This is link 51c9b242-e449-44d8-9289-1a5d474d5fbb
</a>
<ul class="h-12"></ul>
</li>
<li class="bg-gray-100">
<a href="#" rel="noreferrer" class="bg-gray-100">
This is link eb479051-0dff-4d8f-9456-bb8b56ab90af
</a>
<img
class="ml-3 text-gray-900 font-medium text-base"
alt="Profile picture of user 17d797ac-aea7-4154-b522-f0ac89762b0b"
src="https://example.org/pictures/55e31a5b-2fcb-4b35-832c-fad711717f1a"
/>
<ul class="font-medium hover:text-gray-700 text-gray-500 ml-4 text-sm"></ul>
</li>
</ul>
<ul class="space-y-6 border-t py-6 border-gray-200 px-4">
<li class="md:hidden z-40 relative">
<ol class="h-7 w-7 mx-auto items-center justify-center rounded-full flex"></ol>
<ol class="items-center flex rounded-full w-7 mx-auto h-7 justify-center"></ol>
<a
href="#"
target="_blank"
class="sm:py-32 lg:px-8 max-w-7xl mx-auto px-4 py-24 sm:px-6 relative"
>
This is link 5014471c-4f44-4696-a1f6-7817d57827eb
</a>
<img
class="h-5 group-hover:text-gray-500 w-5 ml-2"
alt="Profile picture of user 98731873-0af8-4823-9115-f1333a2cdc2e"
src="https://example.org/pictures/12ac8cbf-4540-49ff-a71f-4b0ac684b374"
/>
</li>
<li
class="focus:outline-none font-medium px-4 justify-center text-sm focus:ring-2 border hover:bg-gray-50 focus:ring-offset-2 py-2 border-gray-300 bg-white inline-flex text-gray-700 rounded-md shadow-sm focus:ring-gray-900"
>
<img
class="bg-white focus:ring-indigo-500 text-base focus:outline-none focus:ring-2 items-center font-medium focus:ring-offset-2 hover:text-gray-900 rounded-md group inline-flex"
alt="Profile picture of user 3ae3670d-c729-41d2-adee-691340673aef"
src="https://example.org/pictures/5f289b87-efc2-4cc9-9061-21883b0778db"
/>
</li>
<li class="text-base text-gray-500 mt-6 font-medium text-center">
<ol class="text-sm font-medium text-indigo-600 hover:text-indigo-500"></ol>
<ol class="rounded-md h-6 w-6 inline-block"></ol>
</li>
<li class="hidden lg:flex lg:items-center">
<ol class="border-indigo-600"></ol>
</li>
</ul>
</li>
<li class="sr-only">
<img
class="justify-center py-2 bg-white flex"
alt="Profile picture of user 89195190-9a42-4826-89ba-e7bc1d677520"
src="https://example.org/pictures/742d9c44-1c75-461f-b2d0-e52636041966"
/>
<img
class="-ml-14 sticky left-0 z-20 text-gray-400 -mt-2.5 leading-5 pr-2 w-14 text-right text-xs"
alt="Profile picture of user 77a37b14-1c0f-4ef0-85d4-a18755065ea3"
src="https://example.org/pictures/8ad2e05f-6ea6-4d94-8f82-f8aa59274b9e"
/>
</li>
</ol>
<img
class="border-t shadow-sm sm:border bg-white sm:rounded-lg border-gray-200 border-b"
alt="Profile picture of user 07935cf2-78e5-49ba-afdb-09d13c8320d6"
src="https://example.org/pictures/8f895991-6e8c-4bcb-82c3-11c2a3338eb2"
/>
<ol class="grid-cols-2 grid gap-x-8 gap-y-10">
<li
class="py-1 w-48 shadow-lg bg-white rounded-md ring-1 absolute ring-black z-10 focus:outline-none right-0 mt-2 ring-opacity-5 origin-top-right"
>
<ol class="h-6 w-6">
<li class="bg-gray-500 inset-0 transition-opacity fixed bg-opacity-75">
<ol class="flex"></ol>
<a href="#" class="mt-2 flex items-center justify-between">
This is link 1a1a3c60-a2ea-4153-acd7-12aa82f03c8d
</a>
<a href="#" target="_blank" class="text-gray-300 flex-shrink-0 w-5 h-5">
This is link bdaa695c-3fe4-4cab-8d68-dbb338601044
</a>
</li>
<li class="text-gray-500">
<ol class="py-20"></ol>
<a href="#" target="_blank" class="text-sm ml-3">
This is link 2183c71d-39ec-42d4-8b1e-eeb7e5490050
</a>
</li>
<li class="mt-10">
<ol class="group"></ol>
<a href="#" rel="noreferrer" class="bg-gray-50">
This is link c49882fd-ab55-41a4-8dac-b96fe2901bce
</a>
<ol class="bg-white h-[940px] overflow-y-auto"></ol>
</li>
<li class="flex-1 space-y-1">
<ol class="h-6 w-6"></ol>
</li>
</ol>
<ul class="z-10 flex relative items-center lg:hidden">
<li class="aspect-w-1 bg-gray-100 rounded-lg overflow-hidden aspect-h-1">
<img
class="overflow-hidden sm:rounded-md bg-white shadow"
alt="Profile picture of user 263bb89c-5b54-4247-8c82-fec829b1a895"
src="https://example.org/pictures/4c12a5c4-d117-4f81-93e1-47a45626a36e"
/>
<a href="#" class="sr-only"> This is link fc3885d8-d63f-4455-b056-f113aa3a2f23 </a>
<ol
class="border-t grid-cols-1 border-gray-200 gap-6 border-b mt-6 sm:grid-cols-2 grid py-6"
></ol>
</li>
<li class="space-x-3 items-center flex">
<a href="#" class="py-2 bg-white">
This is link 1d718cb1-05e3-4d14-b959-92f5fd475ce0
</a>
<img
class="rounded-md w-full focus:border-indigo-500 border-gray-300 focus:ring-indigo-500 block mt-1 shadow-sm sm:text-sm"
alt="Profile picture of user 1c4d6dc3-700a-4167-8ec3-3dc2f73d4ad5"
src="https://example.org/pictures/359599b3-5610-482e-bc09-025ac5283170"
/>
<ul class="max-w-3xl mx-auto divide-y-2 divide-gray-200"></ul>
<ul class="flex space-x-4"></ul>
</li>
<li class="text-gray-500 hover:text-gray-600">
<ul class="h-96 w-full relative lg:hidden"></ul>
<ol class="text-gray-500 mt-6 text-sm"></ol>
<a href="#" class="sm:col-span-6">
This is link 51cc68af-1184-4f8c-9efd-d2f855902b0b
</a>
<ol
class="left-0 inset-y-0 absolute pointer-events-none pl-3 items-center flex"
></ol>
</li>
<li class="flex-shrink-0">
<ol
class="shadow-lg rounded-lg ring-1 bg-white ring-black ring-opacity-5 divide-gray-50 divide-y-2"
></ol>
</li>
</ul>
<img
class="pl-3 sm:pr-6 py-3.5 relative pr-4"
alt="Profile picture of user 095f88c2-1892-41d1-8165-981ab47b1942"
src="https://example.org/pictures/c70d575b-353a-4360-99df-c2100a36e41b"
/>
</li>
<li class="whitespace-nowrap">
<a href="#" target="_blank" rel="noreferrer" class="h-full">
This is link c804eb7a-39ea-46e6-a79a-2d48e61d5b4e
</a>
</li>
<li class="text-gray-900 text-2xl font-bold tracking-tight">
<img
class="text-gray-900 font-medium"
alt="Profile picture of user 12190673-25cb-4175-90d7-73282e51bd02"
src="https://example.org/pictures/e55a076b-0325-4629-8e02-c9491f2f49cc"
/>
</li>
<li
class="ring-black sm:-mx-6 overflow-hidden ring-1 ring-opacity-5 mt-8 md:rounded-lg md:mx-0 -mx-4 shadow"
>
<ol class="flex items-center font-medium hover:text-gray-800 text-sm text-gray-700">
<li class="flex mt-8 flex-col">
<ol class="sm:inline hidden"></ol>
<ul class="flex items-center absolute inset-0"></ul>
<ol class="h-6 w-6"></ol>
</li>
<li class="-ml-2 rounded-md text-gray-400 p-2 bg-white">
<img
class="block ml-3 font-medium text-sm text-gray-700"
alt="Profile picture of user 2cee83e8-6405-4046-9454-7f6083db307d"
src="https://example.org/pictures/93097140-2e56-4a3f-bc41-e00c658b7767"
/>
<ul class="sm:grid font-medium hidden grid-cols-4 text-gray-600 mt-6 text-sm"></ul>
<ul class="h-64 w-64 rounded-full xl:h-80 xl:w-80"></ul>
<ul class="sr-only"></ul>
</li>
</ol>
<img
class="aspect-w-2 group sm:aspect-w-1 aspect-h-1 overflow-hidden sm:aspect-h-1 sm:row-span-2 rounded-lg"
alt="Profile picture of user bc370a72-a44e-44e1-bbec-962c234060da"
src="https://example.org/pictures/d284630d-a088-49ad-a018-b245a8d7acb7"
/>
</li>
<li class="space-y-6 mt-6">
<ul class="w-5 text-gray-400 h-5">
<li
class="hover:bg-gray-100 py-1.5 bg-gray-50 text-gray-400 focus:z-10 rounded-tl-lg"
>
<img
class="bg-gray-50 py-1.5 hover:bg-gray-100 focus:z-10 text-gray-400"
alt="Profile picture of user 4b298841-5911-4b70-ae5a-a5aa8646e575"
src="https://example.org/pictures/fd05d4f7-2b6c-4de4-b7a7-3035e7b5fe78"
/>
<img
class="px-3 py-2 bg-white relative"
alt="Profile picture of user 5406aa7d-5563-4ce1-980f-754a912cd9ff"
src="https://example.org/pictures/e979117e-580c-40a7-be74-bf1b916b9ac0"
/>
<a
href="#"
target="_blank"
rel="noreferrer"
class="mt-2 font-medium text-gray-900 text-lg"
>
This is link 95d285c9-6e4e-43c2-a97f-bb958dcce92f
</a>
<ul class="sr-only"></ul>
</li>
<li class="mt-2 text-sm text-gray-500">
<a href="#" target="_blank" class="text-sm text-blue-gray-900 font-medium block">
This is link e0ca5c13-efa5-44d7-b91e-512df7c88410
</a>
<img
class="w-5 h-5"
alt="Profile picture of user 4f5aeddc-6718-40fd-87d2-dcca7e4ef8b1"
src="https://example.org/pictures/6a2ed606-6c59-413c-911f-612c7390091f"
/>
<ol class="font-medium text-gray-900"></ol>
<ol class="justify-center rounded-full h-7 items-center mx-auto w-7 flex"></ol>
</li>
<li class="font-medium px-1 whitespace-nowrap py-4 text-sm border-b-2">
<a
href="#"
target="_blank"
class="min-h-80 rounded-md aspect-h-1 aspect-w-1 lg:aspect-none w-full group-hover:opacity-75 lg:h-80 overflow-hidden bg-gray-200"
>
This is link 4f52e535-2e46-44e2-8a5f-fce48c1a673a
</a>
<ul class="text-gray-300 h-full w-full"></ul>
<img
class="block"
alt="Profile picture of user 76dd6af3-d2b7-4a7a-8bc3-628ce95ea9b3"
src="https://example.org/pictures/c6b476c8-283c-44f7-a5c6-4cb48c5ae10b"
/>
<ol class="h-32 relative lg:hidden w-full"></ol>
</li>
<li class="bg-gray-100 z-10 sticky sm:pt-3 pl-1 pt-1 md:hidden sm:pl-3 top-0">
<ol class="z-40 relative lg:hidden"></ol>
<a href="#" class="sr-only"> This is link ec11a608-55b8-41fd-8085-325252c469af </a>
<ol class="divide-gray-200 lg:col-span-9 divide-y"></ol>
</li>
</ul>
<ul class="text-xl font-semibold ml-1">
<li class="lg:flex-1 lg:w-0">
<ol class="sm:hidden"></ol>
<img
class="block py-2 text-blue-gray-900 text-base hover:bg-blue-gray-50 font-medium px-3 rounded-md"
alt="Profile picture of user 885eb4a3-98ca-4614-b255-ec03e124c026"
src="https://example.org/pictures/6d3a9136-583e-4742-826f-e6d94f60ce99"
/>
</li>
<li class="truncate w-0 ml-2 flex-1">
<img
class="hover:text-gray-600 text-gray-500"
alt="Profile picture of user de38ffb5-607b-4ed6-87bd-dd05fde1731c"
src="https://example.org/pictures/da67f442-40c9-425d-9344-2f9772582519"
/>
<ol class="text-center mt-8 text-gray-400 text-base"></ol>
</li>
</ul>
</li>
</ol>
</li>
<li class="lg:block hidden lg:flex-1">
<a href="#" target="_blank" class="text-base ml-3 text-gray-500">
This is link 9a44892f-7ead-48b6-af94-913ec04821a1
</a>
<ol
class="shadow-sm border-gray-300 focus:ring-indigo-500 sm:text-sm focus:border-indigo-500 w-full rounded-md block"
>
<li class="bg-white hover:bg-gray-100 py-1.5 focus:z-10">
<a
href="#"
target="_blank"
class="bg-gray-200 text-gray-700 gap-px lg:flex-none border-b text-center grid-cols-7 grid text-xs leading-6 border-gray-300 font-semibold"
>
This is link 42a16c66-508d-4d65-bb1a-b252f7e78df2
</a>
<a href="#" class="h-8 w-auto"> This is link 021bda37-522f-413d-af3c-4a8c4f4d666a </a>
<img
class="max-h-12"
alt="Profile picture of user a1a859bc-ef5e-4349-99ab-a8f4ae262d94"
src="https://example.org/pictures/da93a022-cbbf-4ba0-b34c-21c090d87d74"
/>
<a
href="#"
target="_blank"
rel="noreferrer"
class="flex relative justify-center text-sm"
>
This is link fa5aded2-2906-4809-b742-26832b226f50
</a>
</li>
<li class="py-12 sm:px-6 lg:py-16 px-4 lg:px-8 mx-auto max-w-7xl">
<a href="#" rel="noreferrer" class="min-w-0 ml-3 flex-1">
This is link 715e397d-5676-42fc-9d8f-456172543c31
</a>
<img
class="bg-gray-800"
alt="Profile picture of user c02575ab-6ff1-45f9-a8e3-242c79b133fc"
src="https://example.org/pictures/3d8ecc7a-2504-4797-a644-f93d74acf853"
/>
<img
class="text-base text-gray-900 font-medium"
alt="Profile picture of user 9f457701-be79-4ff2-9dfc-9382dafb20ab"
src="https://example.org/pictures/438b713b-c9fd-4da2-a265-4b8d8f531e30"
/>
<a href="#" rel="noreferrer" class="text-sm">
This is link 4c84098a-c0ac-4044-bbba-6f10bcc315fb
</a>
</li>
<li class="w-6 flex-shrink-0 h-6 text-green-500">
<img
class="mx-auto sm:px-6 px-4 max-w-7xl"
alt="Profile picture of user 00ae83b8-845d-447e-ae1b-cd3c685e1ca0"
src="https://example.org/pictures/b9deba2b-c5b3-4b80-bf76-e0a717d310fd"
/>
<ul class="w-72">
<li class="sm:flex sm:justify-between sm:items-center">
<img
class="block font-medium text-sm text-gray-700"
alt="Profile picture of user c0de8cb0-e9d7-4639-8c3d-851ca17e665b"
src="https://example.org/pictures/897193ff-aa53-4e9e-b761-bc4f75aef572"
/>
</li>
<li class="text-sm hidden font-medium ml-3 text-gray-700 lg:block">
<img
class="h-8 w-auto"
alt="Profile picture of user 9ff9c8ac-2374-4960-9996-ec258745c91a"
src="https://example.org/pictures/e898638f-08fa-4743-aea7-66adba84bded"
/>
</li>
<li
class="mx-auto sm:px-6 px-4 lg:items-center lg:flex lg:py-16 lg:px-8 max-w-7xl py-12"
>
<img
class="lg:max-w-none px-4 max-w-2xl mx-auto lg:px-0"
alt="Profile picture of user 7c4d617d-afa2-4410-81c5-2def540d2d20"
src="https://example.org/pictures/05a7dbc1-c1cc-4f99-99e5-7b507a4108b3"
/>
<ul class="hover:text-gray-600 text-gray-500"></ul>
<ul
class="relative rounded-md border-transparent focus-within:ring-2 focus-within:ring-white -ml-2 group"
></ul>
</li>
</ul>
<img
class="h-5 text-gray-300 w-5"
alt="Profile picture of user bf2c6905-715a-4e38-9b5d-17fd8e7aec2a"
src="https://example.org/pictures/e759a4d7-5e63-4075-ba32-c6574107f401"
/>
</li>
<li class="object-cover object-center h-full w-full">
<ol class="w-full">
<li class="md:mt-0 absolute sm:-mt-32 -mt-72 inset-0">
<ul class="w-12 h-12 rounded-full"></ul>
</li>
<li class="h-1.5 rounded-full w-1.5 mb-1 mx-0.5 bg-gray-400">
<ol class="border-gray-200 border-4 rounded-lg border-dashed h-96"></ol>
<img
class="order-1 font-semibold text-gray-700"
alt="Profile picture of user 1c2cabee-08a3-4b48-ba54-5a578b2c3d30"
src="https://example.org/pictures/f5c185be-8b9d-490d-81f4-73a6e1517d98"
/>
<img
class="font-bold sm:text-4xl text-gray-900 tracking-tight text-3xl leading-8 text-center"
alt="Profile picture of user 1e8a2508-a37d-4f6b-9a8c-c6fcf0773604"
src="https://example.org/pictures/90af1bd2-30ed-45d3-917f-c685190ce56e"
/>
</li>
<li class="space-x-2 mt-4 text-sm text-gray-700 flex">
<ul
class="rounded-full translate-x-1/2 block transform border-2 absolute bottom-0 right-0 border-white translate-y-1/2"
></ul>
<ol
class="sm:hidden text-base py-2 text-gray-900 w-full placeholder-gray-500 h-full focus:outline-none border-transparent pr-3 pl-8 focus:placeholder-gray-400 focus:ring-0 focus:border-transparent"
></ol>
</li>
</ol>
<ul class="lg:mt-0 self-center flow-root mt-8">
<li
class="justify-center rounded-full bg-transparent bg-white hover:text-gray-500 focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 text-gray-400 inline-flex focus:outline-none h-8 items-center w-8"
>
<ol class="block xl:inline"></ol>
<ol class="flex-shrink-0 ml-4"></ol>
<ol class="text-gray-200"></ol>
<img
class="border-gray-300 focus:relative md:w-9 rounded-r-md flex bg-white md:hover:bg-gray-50 pl-4 text-gray-400 border hover:text-gray-500 items-center pr-3 border-l-0 justify-center md:px-2 py-2"
alt="Profile picture of user b0a1e2d3-84c4-494b-91d6-194fc294b0db"
src="https://example.org/pictures/2f414511-756c-40ef-aded-22e3f4d985d7"
/>
</li>
<li class="inset-0 absolute">
<img
class="text-gray-500 text-base font-medium text-center"
alt="Profile picture of user 431f88eb-5002-43ab-b23a-37ae0ef7d424"
src="https://example.org/pictures/bc7c19bb-4ef2-46ff-b00e-da70febab926"
/>
<img
class="inset-0 absolute z-10"
alt="Profile picture of user 4a3698d0-7cea-4ba2-854d-28b2bb2d374b"
src="https://example.org/pictures/6078c4cd-db51-43af-90b3-8aeb0a8f1030"
/>
</li>
</ul>
<a href="#" rel="noreferrer" class="bg-white">
This is link ab01c689-e03a-4992-8322-37c20551cb07
</a>
</li>
<li class="flex px-4 pb-2 pt-5">
<ol
class="sm:px-6 px-4 bg-white relative pb-8 md:p-6 shadow-2xl items-center flex w-full sm:pt-8 overflow-hidden lg:p-8 pt-14"
>
<li class="py-1.5 hover:bg-gray-100 focus:z-10 text-gray-400 bg-gray-50">
<a
href="#"
rel="noreferrer"
class="sm:text-sm bg-gray-50 items-center border-gray-300 border-r-0 rounded-l-md text-gray-500 inline-flex border px-3"
>
This is link 3ef75acd-3dfc-4e82-801b-eae9ff7c4351
</a>
<ol class="absolute border-dashed border-gray-200 border-2 rounded-lg inset-0"></ol>
</li>
<li class="hover:bg-gray-50 block">
<ol class="items-center flex justify-center p-8"></ol>
<ul class="mx-auto sm:px-6 lg:px-8 pb-12 max-w-7xl px-4"></ul>
<img
class="h-6 w-6 text-green-400"
alt="Profile picture of user f5900afb-7bee-4492-b6e3-148f0afc4f5f"
src="https://example.org/pictures/1b12c3fb-9f84-4cc7-84a7-d38f7ea232ee"
/>
</li>
<li class="flex mt-4 lg:flex-grow-0 flex-grow lg:ml-4 flex-shrink-0 ml-8">
<img
class="focus:ring-indigo-500 block w-full sm:text-sm border-gray-300 focus:border-indigo-500 rounded-md shadow-sm"
alt="Profile picture of user c9dd7fa0-c1f4-477c-b24e-87d200ebb161"
src="https://example.org/pictures/1a3b2e5c-a192-47f3-a550-e18f715213a2"
/>
<a href="#" target="_blank" rel="noreferrer" class="text-gray-300 hover:text-white">
This is link 81b819f3-b2e8-41db-ab8d-abe8c6926047
</a>
</li>
</ol>
<img
class="lg:flex-1 lg:block hidden"
alt="Profile picture of user 352e9ea2-0216-4a0c-917c-1906f5ef4ed1"
src="https://example.org/pictures/ec02985c-b92d-4978-906e-7bdc70bfa54e"
/>
</li>
</ol>
</li>
<li class="sr-only">
<a href="#" target="_blank" class="text-gray-500 mt-4 text-sm">
This is link a712361b-f51e-4f0a-9d55-b64a5d53e10e
</a>
<a
href="#"
rel="noreferrer"
class="rounded-lg ring-opacity-5 shadow-lg overflow-hidden ring-1 ring-black"
>
This is link 8eb412d6-1c39-4fed-b507-cf2a65904247
</a>
</li>
</ul>
</nav>
<img
class="bg-gray-100"
alt="Profile picture of user 8825a6f0-3a41-44b8-92ec-abcd0af79bfb"
src="https://example.org/pictures/679e2a54-073e-416a-85c4-3eba0626aab3"
/>
<span class="bg-white focus:z-10 py-1.5 hover:bg-gray-100">
This is text 3d190171-53b3-4393-99ab-9bedfc964141
</span>
</div>

View file

@ -1,11 +1,17 @@
use fxhash::{FxHashMap, FxHashSet};
use glob_match::glob_match;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use tracing::event;
use crate::GlobEntry;
#[derive(Debug, Clone, PartialEq)]
pub struct GlobEntry {
/// Base path of the glob
pub base: String,
pub fn hoist_static_glob_parts(entries: &Vec<GlobEntry>) -> Vec<GlobEntry> {
/// Glob pattern
pub pattern: String,
}
pub fn hoist_static_glob_parts(entries: &Vec<GlobEntry>, emit_parent_glob: bool) -> Vec<GlobEntry> {
let mut result = vec![];
for entry in entries {
@ -40,7 +46,7 @@ pub fn hoist_static_glob_parts(entries: &Vec<GlobEntry>) -> Vec<GlobEntry> {
// If the base path is a file, then we want to move the file to the pattern, and point the
// directory to the base. This is necessary for file watchers that can only listen to
// folders.
if pattern.is_empty() && base.is_file() {
if emit_parent_glob && pattern.is_empty() && base.is_file() {
result.push(GlobEntry {
// SAFETY: `parent()` will be available because we verify `base` is a file, thus a
// parent folder exists.
@ -83,7 +89,7 @@ pub fn hoist_static_glob_parts(entries: &Vec<GlobEntry>) -> Vec<GlobEntry> {
/// tailwind --pwd ./project/components --content "**/*.js"
/// ```
pub fn optimize_patterns(entries: &Vec<GlobEntry>) -> Vec<GlobEntry> {
let entries = hoist_static_glob_parts(entries);
let entries = hoist_static_glob_parts(entries, true);
// Track all base paths and their patterns. Later we will turn them back into `GlobalEntry`s.
let mut pattern_map: FxHashMap<String, FxHashSet<String>> = FxHashMap::default();
@ -132,11 +138,23 @@ pub fn optimize_patterns(entries: &Vec<GlobEntry>) -> Vec<GlobEntry> {
// using `*`.
//
// E.g.:
// Original input: `../project-b/**/*.{html,js}`
// Expanded input: `../project-b/**/*.html` & `../project-b/**/*.js`
// Split on first input: ("../project-b", "**/*.html")
// Split on second input: ("../project-b", "**/*.js")
fn split_pattern(pattern: &str) -> (Option<String>, Option<String>) {
//
// Input:
// - `../project-b/**/*.html`
// - `../project-b/**/*.js`
//
// Split results in:
// - `("../project-b", "**/*.html")`
// - `("../project-b", "**/*.js")`
//
// A static file glob should also be considered as a dynamic part.
//
// E.g.:
//
// Input: `../project-b/foo/bar.html`
// Split results in: `("../project-b/foo", "bar.html")`
//
pub fn split_pattern(pattern: &str) -> (Option<String>, Option<String>) {
// No dynamic parts, so we can just return the input as-is.
if !pattern.contains('*') {
return (Some(pattern.to_owned()), None);
@ -168,19 +186,12 @@ fn split_pattern(pattern: &str) -> (Option<String>, Option<String>) {
(static_part, dynamic_part)
}
pub fn path_matches_globs(path: &Path, globs: &[GlobEntry]) -> bool {
let path = path.to_string_lossy();
globs
.iter()
.any(|g| glob_match(&format!("{}/{}", g.base, g.pattern), &path))
}
#[cfg(test)]
mod tests {
use super::optimize_patterns;
use crate::GlobEntry;
use bexpand::Expression;
use pretty_assertions::assert_eq;
use std::process::Command;
use std::{fs, path};
use tempfile::tempdir;

View file

@ -1,478 +1,12 @@
use crate::glob::hoist_static_glob_parts;
use crate::parser::Extractor;
use crate::scanner::allowed_paths::resolve_paths;
use crate::scanner::detect_sources::DetectSources;
use bexpand::Expression;
use bstr::ByteSlice;
use fxhash::{FxHashMap, FxHashSet};
use glob::optimize_patterns;
use glob_match::glob_match;
use paths::Path;
use rayon::prelude::*;
use scanner::allowed_paths::read_dir;
use std::fs;
use std::path::PathBuf;
use std::sync;
use std::time::SystemTime;
use tracing::event;
pub mod cursor;
pub mod extractor;
pub mod fast_skip;
pub mod glob;
pub mod parser;
pub mod paths;
pub mod scanner;
pub mod throughput;
static SHOULD_TRACE: sync::LazyLock<bool> = sync::LazyLock::new(
|| matches!(std::env::var("DEBUG"), Ok(value) if value.eq("*") || value.eq("1") || value.eq("true") || value.contains("tailwind")),
);
fn init_tracing() {
if !*SHOULD_TRACE {
return;
}
_ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE)
.compact()
.try_init();
}
#[derive(Debug, Clone)]
pub struct ChangedContent {
pub file: Option<PathBuf>,
pub content: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ScanOptions {
/// Base path to start scanning from
pub base: Option<String>,
/// Glob sources
pub sources: Vec<GlobEntry>,
}
#[derive(Debug, Clone)]
pub struct ScanResult {
pub candidates: Vec<String>,
pub files: Vec<String>,
pub globs: Vec<GlobEntry>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GlobEntry {
pub base: String,
pub pattern: String,
}
#[derive(Debug, Clone, Default)]
pub struct Scanner {
/// Glob sources
sources: Option<Vec<GlobEntry>>,
/// Scanner is ready to scan. We delay the file system traversal for detecting all files until
/// we actually need them.
ready: bool,
/// All files that we have to scan
files: Vec<PathBuf>,
/// All directories, sub-directories, etc… we saw during source detection
dirs: Vec<PathBuf>,
/// All generated globs
globs: Vec<GlobEntry>,
/// Track file modification times
mtimes: FxHashMap<PathBuf, SystemTime>,
/// Track unique set of candidates
candidates: FxHashSet<String>,
}
impl Scanner {
pub fn new(sources: Option<Vec<GlobEntry>>) -> Self {
Self {
sources,
..Default::default()
}
}
pub fn scan(&mut self) -> Vec<String> {
init_tracing();
self.prepare();
self.check_for_new_files();
self.compute_candidates();
let mut candidates: Vec<String> = self.candidates.clone().into_iter().collect();
candidates.sort();
candidates
}
#[tracing::instrument(skip_all)]
pub fn scan_content(&mut self, changed_content: Vec<ChangedContent>) -> Vec<String> {
self.prepare();
let candidates = parse_all_blobs(read_all_files(changed_content));
let mut new_candidates = vec![];
for candidate in candidates {
if self.candidates.contains(&candidate) {
continue;
}
self.candidates.insert(candidate.clone());
new_candidates.push(candidate);
}
new_candidates
}
#[tracing::instrument(skip_all)]
pub fn get_candidates_with_positions(
&mut self,
changed_content: ChangedContent,
) -> Vec<(String, usize)> {
self.prepare();
let content = read_changed_content(changed_content).unwrap_or_default();
let extractor = Extractor::with_positions(&content[..], Default::default());
let candidates: Vec<(String, usize)> = extractor
.into_iter()
.map(|(s, i)| {
// SAFETY: When we parsed the candidates, we already guaranteed that the byte slices
// are valid, therefore we don't have to re-check here when we want to convert it back
// to a string.
unsafe { (String::from_utf8_unchecked(s.to_vec()), i) }
})
.collect();
candidates
}
#[tracing::instrument(skip_all)]
pub fn get_files(&mut self) -> Vec<String> {
self.prepare();
self.files
.iter()
.filter_map(|x| Path::from(x.clone()).canonicalize().ok())
.map(|x| x.to_string())
.collect()
}
#[tracing::instrument(skip_all)]
pub fn get_globs(&mut self) -> Vec<GlobEntry> {
self.prepare();
self.globs.clone()
}
#[tracing::instrument(skip_all)]
fn compute_candidates(&mut self) {
let mut changed_content = vec![];
for path in &self.files {
let current_time = fs::metadata(path)
.and_then(|m| m.modified())
.unwrap_or(SystemTime::now());
let previous_time = self.mtimes.insert(path.clone(), current_time);
let should_scan_file = match previous_time {
// Time has changed, so we need to re-scan the file
Some(prev) if prev != current_time => true,
// File was in the cache, no need to re-scan
Some(_) => false,
// File didn't exist before, so we need to scan it
None => true,
};
if should_scan_file {
changed_content.push(ChangedContent {
file: Some(path.clone()),
content: None,
});
}
}
if !changed_content.is_empty() {
let candidates = parse_all_blobs(read_all_files(changed_content));
self.candidates.extend(candidates);
}
}
// Ensures that all files/globs are resolved and the scanner is ready to scan
// content for candidates.
fn prepare(&mut self) {
if self.ready {
return;
}
self.scan_sources();
self.ready = true;
}
#[tracing::instrument(skip_all)]
fn check_for_new_files(&mut self) {
let mut modified_dirs: Vec<PathBuf> = vec![];
// Check all directories to see if they were modified
for path in &self.dirs {
let current_time = fs::metadata(path)
.and_then(|m| m.modified())
.unwrap_or(SystemTime::now());
let previous_time = self.mtimes.insert(path.clone(), current_time);
let should_scan = match previous_time {
// Time has changed, so we need to re-scan the file
Some(prev) if prev != current_time => true,
// File was in the cache, no need to re-scan
Some(_) => false,
// File didn't exist before, so we need to scan it
None => true,
};
if should_scan {
modified_dirs.push(path.clone());
}
}
// Scan all modified directories for their immediate files
let mut known = FxHashSet::from_iter(self.files.iter().chain(self.dirs.iter()).cloned());
while !modified_dirs.is_empty() {
let new_entries = modified_dirs
.iter()
.flat_map(|dir| read_dir(dir, Some(1)))
.map(|entry| entry.path().to_owned())
.filter(|path| !known.contains(path))
.collect::<Vec<_>>();
modified_dirs.clear();
for path in new_entries {
if path.is_file() {
known.insert(path.clone());
self.files.push(path);
} else if path.is_dir() {
known.insert(path.clone());
self.dirs.push(path.clone());
// Recursively scan the new directory for files
modified_dirs.push(path);
}
}
}
}
#[tracing::instrument(skip_all)]
fn scan_sources(&mut self) {
let Some(sources) = &self.sources else {
return;
};
if sources.is_empty() {
return;
}
// Expand glob patterns and create new `GlobEntry` instances for each expanded pattern.
let sources = sources
.iter()
.flat_map(|source| {
let expression: Result<Expression, _> = source.pattern[..].try_into();
let Ok(expression) = expression else {
return vec![source.clone()];
};
expression
.into_iter()
.filter_map(Result::ok)
.map(move |pattern| GlobEntry {
base: source.base.clone(),
pattern: pattern.into(),
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
// Partition sources into sources that should be promoted to auto source detection and
// sources that should be resolved as globs.
let (auto_sources, glob_sources): (Vec<_>, Vec<_>) = sources.iter().partition(|source| {
// If a glob ends with `/**/*`, then we just want to register the base path as a new
// base. Essentially converting it to use auto source detection.
if source.pattern.ends_with("**/*") {
return true;
}
// Directories should be promoted to auto source detection
if PathBuf::from(&source.base).join(&source.pattern).is_dir() {
return true;
}
false
});
fn join_paths(a: &str, b: &str) -> PathBuf {
let mut tmp = a.to_owned();
let b = b.trim_end_matches("**/*").trim_end_matches('/');
if b.starts_with('/') {
return PathBuf::from(b);
}
// On Windows a path like C:/foo.txt is absolute but C:foo.txt is not
// (the 2nd is relative to the CWD)
if b.chars().nth(1) == Some(':') && b.chars().nth(2) == Some('/') {
return PathBuf::from(b);
}
tmp += "/";
tmp += b;
PathBuf::from(&tmp)
}
for path in auto_sources.iter().filter_map(|source| {
dunce::canonicalize(join_paths(&source.base, &source.pattern)).ok()
}) {
// Insert a glob for the base path, so we can see new files/folders in the directory itself.
self.globs.push(GlobEntry {
base: path.to_string_lossy().into(),
pattern: "*".into(),
});
// Detect all files/folders in the directory
let detect_sources = DetectSources::new(path);
let (files, globs, dirs) = detect_sources.detect();
self.files.extend(files);
self.globs.extend(globs);
self.dirs.extend(dirs);
}
// Turn `Vec<&GlobEntry>` in `Vec<GlobEntry>`
let glob_sources: Vec<_> = glob_sources.into_iter().cloned().collect();
let hoisted = hoist_static_glob_parts(&glob_sources);
for source in &hoisted {
// If the pattern is empty, then the base points to a specific file or folder already
// if it doesn't contain any dynamic parts. In that case we can use the base as the
// pattern.
//
// Otherwise we need to combine the base and the pattern, otherwise a pattern that
// looks like `*.html`, will never match a path that looks like
// `/my-project/project-a/index.html`, because it contains `/`.
//
// We can't prepend `**/`, because then `/my-project/project-a/nested/index.html` would
// match as well.
//
// Instead we combine the base and the pattern as a single glob pattern.
let mut full_pattern = source.base.clone().replace('\\', "/");
if !source.pattern.is_empty() {
full_pattern.push('/');
full_pattern.push_str(&source.pattern);
}
let base = PathBuf::from(&source.base);
for entry in resolve_paths(&base) {
let Some(file_type) = entry.file_type() else {
continue;
};
if !file_type.is_file() {
continue;
}
let file_path = entry.into_path();
let Some(file_path_str) = file_path.to_str() else {
continue;
};
let file_path_str = file_path_str.replace('\\', "/");
if glob_match(&full_pattern, &file_path_str) {
self.files.push(file_path);
}
}
}
self.globs.extend(hoisted);
// Re-optimize the globs to reduce the number of patterns we have to scan.
self.globs = optimize_patterns(&self.globs);
}
}
fn read_changed_content(c: ChangedContent) -> Option<Vec<u8>> {
if let Some(content) = c.content {
return Some(content.into_bytes());
}
let Some(file) = c.file else {
return Default::default();
};
let Ok(content) = std::fs::read(&file).map_err(|e| {
event!(tracing::Level::ERROR, "Failed to read file: {:?}", e);
e
}) else {
return Default::default();
};
let Some(extension) = file.extension().map(|x| x.to_str()) else {
return Some(content);
};
match extension {
Some("svelte") => Some(content.replace(" class:", " ")),
_ => Some(content),
}
}
#[tracing::instrument(skip_all)]
fn read_all_files(changed_content: Vec<ChangedContent>) -> Vec<Vec<u8>> {
event!(
tracing::Level::INFO,
"Reading {:?} file(s)",
changed_content.len()
);
changed_content
.into_par_iter()
.filter_map(read_changed_content)
.collect()
}
#[tracing::instrument(skip_all)]
fn parse_all_blobs(blobs: Vec<Vec<u8>>) -> Vec<String> {
let input: Vec<_> = blobs.iter().map(|blob| &blob[..]).collect();
let input = &input[..];
let mut result: Vec<String> = input
.par_iter()
.map(|input| Extractor::unique(input, Default::default()))
.reduce(Default::default, |mut a, b| {
a.extend(b);
a
})
.into_iter()
.map(|s| {
// SAFETY: When we parsed the candidates, we already guaranteed that the byte slices
// are valid, therefore we don't have to re-check here when we want to convert it back
// to a string.
unsafe { String::from_utf8_unchecked(s.to_vec()) }
})
.collect();
result.sort();
result
}
pub use glob::GlobEntry;
pub use scanner::sources::PublicSourceEntry;
pub use scanner::ChangedContent;
pub use scanner::Scanner;

65
crates/oxide/src/main.rs Normal file
View file

@ -0,0 +1,65 @@
use std::hint::black_box;
use tailwindcss_oxide::cursor::Cursor;
use tailwindcss_oxide::extractor::machine::{Machine, MachineState};
use tailwindcss_oxide::extractor::{Extracted, Extractor};
use tailwindcss_oxide::throughput::Throughput;
fn run_full_extractor(input: &[u8]) -> Vec<&[u8]> {
Extractor::new(input)
.extract()
.into_iter()
.map(|x| match x {
Extracted::Candidate(bytes) => bytes,
Extracted::CssVariable(bytes) => bytes,
})
.collect::<Vec<_>>()
}
fn _run_machine<T: Machine>(input: &[u8]) -> Vec<&[u8]> {
let len = input.len();
let mut machine = T::default();
let mut cursor = Cursor::new(input);
let mut result = Vec::with_capacity(25);
while cursor.pos < len {
if let MachineState::Done(span) = machine.next(&mut cursor) {
result.push(span.slice(input));
}
cursor.advance();
}
result
}
fn run(input: &[u8]) -> Vec<&[u8]> {
// _run_machine::<tailwindcss_oxide::extractor::arbitrary_property_machine::ArbitraryPropertyMachine>(input)
// _run_machine::<tailwindcss_oxide::extractor::arbitrary_value_machine::ArbitraryValueMachine>(input)
// _run_machine::<tailwindcss_oxide::extractor::arbitrary_variable_machine::ArbitraryVariableMachine>(input)
// _run_machine::<tailwindcss_oxide::extractor::candidate_machine::CandidateMachine>(input)
// _run_machine::<tailwindcss_oxide::extractor::css_variable_machine::CssVariableMachine>(input)
// _run_machine::<tailwindcss_oxide::extractor::modifier_machine::ModifierMachine>(input)
// _run_machine::<tailwindcss_oxide::extractor::named_utility_machine::NamedUtilityMachine>(input)
// _run_machine::<tailwindcss_oxide::extractor::named_variant_machine::NamedVariantMachine>(input)
// _run_machine::<tailwindcss_oxide::extractor::string_machine::StringMachine>(input)
// _run_machine::<tailwindcss_oxide::extractor::utility_machine::UtilityMachine>(input)
// _run_machine::<tailwindcss_oxide::extractor::variant_machine::VariantMachine>(input)
run_full_extractor(input)
}
fn main() {
let iterations = 10_000;
let input = include_bytes!("./fixtures/example.html");
let throughput = Throughput::compute(iterations, input.len(), || {
_ = black_box(
input
.split(|x| *x == b'\n')
.flat_map(run)
.collect::<Vec<_>>(),
);
});
eprintln!("Extractor: {:}", throughput);
}

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more