Skip to content

Add @source inline(…) #17147

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 12, 2025
Merged

Add @source inline(…) #17147

merged 3 commits into from
Mar 12, 2025

Conversation

philipp-spiess
Copy link
Member

@philipp-spiess philipp-spiess commented Mar 12, 2025

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:

/* 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:

/* 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:

@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
    

@philipp-spiess philipp-spiess requested a review from a team as a code owner March 12, 2025 12:58
philipp-spiess and others added 2 commits March 12, 2025 14:46
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
@RobinMalfait RobinMalfait enabled auto-merge (squash) March 12, 2025 13:55
@RobinMalfait RobinMalfait merged commit 215f4f3 into main Mar 12, 2025
5 checks passed
@RobinMalfait RobinMalfait deleted the feat/source-inline branch March 12, 2025 13:55
thecrypticace added a commit to tailwindlabs/tailwindcss-intellisense that referenced this pull request Mar 19, 2025
@robsontenorio
Copy link

@philipp-spiess do we have some online docs for this ?

RobinMalfait added a commit that referenced this pull request Mar 25, 2025
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(…)`](#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>
@sudarshan-mane
Copy link

Error: @source paths must be quoted.

@source inline('{hover:,}bg-{slate,gray,zinc,neutral,stone,red,orange,amber,yellow,lime,green,emerald,teal,cyan,sky,blue,indigo,violet,purple,fuchsia,pink,rose}-{50,{100..900..100},950}');
@source inline('{hover:,}text-{slate,gray,zinc,neutral,stone,red,orange,amber,yellow,lime,green,emerald,teal,cyan,sky,blue,indigo,violet,purple,fuchsia,pink,rose}-{50,{100..900..100},950}');
@source inline('{hover:,}border-{slate,gray,zinc,neutral,stone,red,orange,amber,yellow,lime,green,emerald,teal,cyan,sky,blue,indigo,violet,purple,fuchsia,pink,rose}-{50,{100..900..100},950}');
@source inline('ring-{slate,gray,zinc,neutral,stone,red,orange,amber,yellow,lime,green,emerald,teal,cyan,sky,blue,indigo,violet,purple,fuchsia,pink,rose}-{50,{100..900..100},950}');
@source inline('stroke-{slate,gray,zinc,neutral,stone,red,orange,amber,yellow,lime,green,emerald,teal,cyan,sky,blue,indigo,violet,purple,fuchsia,pink,rose}-{50,{100..900..100},950}');
@source inline('fill-{slate,gray,zinc,neutral,stone,red,orange,amber,yellow,lime,green,emerald,teal,cyan,sky,blue,indigo,violet,purple,fuchsia,pink,rose}-{50,{100..900..100},950}');

@philipp-spiess
Copy link
Member Author

philipp-spiess commented Apr 2, 2025

@sudarshan-mane Sounds like you're on the wrong version! Make sure both tailwindcss and @tailwindcss/{postcss,vite,cli} are updated to 4.1.1: https://play.tailwindcss.com/w0lNX41XfJ?file=css

@chrism1996
Copy link

Hi @philipp-spiess is this feature now supported in v4.1.1 ? I want to migrate but we need the safeList feature for going ahead, thanks

@philipp-spiess
Copy link
Member Author

@chrism1996 Yep it's in 4.1 👍

@daniel-perez-f
Copy link

daniel-perez-f commented Apr 15, 2025

@philipp-spiess I have a question related to this source inline:

I dont know if is possible to get the "bg-red" / "bg-yellow" without any value.

Example:

@theme {
    --color-red: oklch(0.577 0.245 27.325);
    --color-red-light: oklch(0.885 0.062 18.334);
    --color-red-dark: oklch(0.444 0.177 26.899);
}

@source inline('{hover:,focus:,}bg-{red,yellow}-{50,{100..900..100},950,light,dark,}');

In this case is generated hover:bg-red-X focus:bg-red-X bg-red-X

With "light" and "dark" works, but if I pass a , without nothing dont generate the bg-red that I want to have it. Any idea how I can do it with this source inline?

Example in Tailwind Play: https://play.tailwindcss.com/YCUsYk1Gmt?file=css

Edit:

I have it working like this in two lines... but can be better if we can send direct a pass a , without nothing

@source inline('{hover:,focus:,}bg-{red}-{50,{100..900..100},950,light,dark}');
@source inline('bg-{red,yellow}');

Thanks.

@thecrypticace
Copy link
Contributor

@daniel-perez-f The problem with the first pattern is that you're generating bg-red-, bg-yellow-, hover:bg-red-, etc… as classes. You can see this hover over the glob pattern in VSCode or in Tailwind Play:
Screenshot 2025-04-15 at 16 55 22

In the correct single line version you have to make sure that the - is omitted as well:

@source inline('{hover:,focus:,}bg-{red,yellow}{,-{50,{100..900..100},950,light,dark}}');

I kinda like the two separate versions — feels more understandable — but you'll want to make sure you generate hover and focus versions as well if the intent is to match the pattern above it:

@source inline('{hover:,focus:,}bg-{red,yellow}-{50,{100..900..100},950,light,dark}');
@source inline('{hover:,focus:,}bg-{red,yellow}');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants