No description
Find a file
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
.github Improve GitHub Actions (#20093) 2026-05-21 12:03:39 +02:00
crates Restrict walking sibling folders when using a pattern (#20263) 2026-06-19 22:39:57 +02:00
integrations Deleting files should not crash Vite HMR (#20259) 2026-06-18 14:29:04 +02:00
packages Update all pnpm dependencies (2026-06-19) (#20262) 2026-06-19 13:11:16 +02:00
patches Bump Lightning CSS (#19771) 2026-03-18 15:57:52 +01:00
playgrounds Update all of react 19.2.6 → 19.2.7 (patch) (#20206) 2026-06-09 10:44:01 +00:00
scripts Make TypeScript a bit more happy (#19124) 2025-10-14 19:52:46 +00:00
.gitignore Fix slow unit test (#17465) 2025-03-31 15:26:01 +02:00
.prettierignore Bump dependencies (#19608) 2026-02-04 12:38:50 +01:00
Cargo.lock Bump NAPI related dependencies (#19982) 2026-04-26 17:49:06 +02:00
Cargo.toml Hoist oxide/crates to just crates (#13333) 2024-03-23 09:00:48 -04:00
CHANGELOG.md Restrict walking sibling folders when using a pattern (#20263) 2026-06-19 22:39:57 +02:00
LICENSE Add README, LICENSE, and CONTRIBUTING (#13088) 2024-03-05 14:45:39 -05:00
package.json Update all pnpm dependencies (2026-06-19) (#20262) 2026-06-19 13:11:16 +02:00
pnpm-lock.yaml Update all pnpm dependencies (2026-06-19) (#20262) 2026-06-19 13:11:16 +02:00
pnpm-workspace.yaml Bump dependencies (#20095) 2026-05-21 17:58:55 +02:00
README.md docs: fix GitHub links to tailwindlabs org (#19686) 2026-02-17 13:06:49 +01:00
rust-toolchain.toml Bump NAPI related dependencies (#19982) 2026-04-26 17:49:06 +02:00
turbo.json Fix segmentation fault when loading @tailwindcss/oxide in a Worker thread (#17276) 2025-03-18 16:28:20 -04:00
vitest.config.ts Bump Vitest to v4 (#19216) 2025-11-20 18:16:20 -05:00

Tailwind CSS

A utility-first CSS framework for rapidly building custom user interfaces.

Build Status Total Downloads Latest Release License


Documentation

For full documentation, visit tailwindcss.com.

Community

For help, discussion about best practices, or feature ideas:

Discuss Tailwind CSS on GitHub

Contributing

If you're interested in contributing to Tailwind CSS, please read our contributing docs before submitting a pull request.