Skip to content

Tailwind v4 @source directive ignores subdirectories with .gitignore #15452

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

Open
jhh opened this issue Dec 19, 2024 · 16 comments
Open

Tailwind v4 @source directive ignores subdirectories with .gitignore #15452

jhh opened this issue Dec 19, 2024 · 16 comments
Assignees
Labels

Comments

@jhh
Copy link

jhh commented Dec 19, 2024

What version of Tailwind CSS are you using?

v4.0.0-beta.8

What build tool (or framework if it abstracts the build tool) are you using?

None, using CLI

What version of Node.js are you using?

v20.18.1

What browser are you using?

N/A

What operating system are you using?

tested on macOS and Linux

Reproduction URL

https://github.com/jhh/tailwind-gitignore

Describe your issue

Adding a .gitignore file to a directory referenced by a @source directive in a Tailwind CSS source file causes the referenced directory to be ignored.

I discovered this because the uv Python packaging tool creates a .gitignore in the .venv directory it uses and I couldn't @source a template directory within it. Manually deleting .venv/.gitigore fixes the issue.

@e-krebs
Copy link

e-krebs commented Dec 28, 2024

this is a documented behavior: https://tailwindcss.com/docs/v4-beta#automatic-source-detection

@jhh
Copy link
Author

jhh commented Dec 28, 2024

I went over that documentation pretty carefully while I was trying to figure out why I wasn't picking up template sources. I think this is still a bug. The docs state that "And if you ever need to explicitly add a source that’s excluded by default, you can always add it with the @source directive...", reinforced by an example @source directive pointing to a directory within node_modules (nearly always .gitignored).

Taken from the repro above, works with one .gitignore:

.
├── .gitignore # ignores ignored/ dir
├── base.css
├── ignored
│   └── template.html # works as documented, @source will pick up classes
├── package-lock.json
├── package.json
├── README.md
└── test.html

Does not work when second gitignore is present in ignored directory:

.
├── .gitignore  # ignores ignored/ dir
├── base.css
├── ignored
│   ├── .gitignore # adding this, ignores *
│   └── template.html # this will not be seen by @source
├── package-lock.json
├── package.json
├── README.md
└── test.html

@e-krebs
Copy link

e-krebs commented Dec 29, 2024

according to the (limited) doc, in neither of those two cases should the template file classesnbe picked up, so that's probably where the bug is.

@RobinMalfait RobinMalfait self-assigned this Jan 6, 2025
@RobinMalfait
Copy link
Member

Hey!

When dealing with gitignored files/folders you have to be very explicit because this can lead to accidental overhead (we had a lot of issues over the years where people accidentally included node_modules which scanned thousands of files).

Let's imagine that you have a plugin in your node_modules, by just adding @source "node_modules"; that would mean that not only your plugin but everything inside node_modules will be scanned.

In your case, the ignored folder is pretty small, so that's not really an issue. However, the only way for us to know whether it's a big or small folder is by actually scanning it.

When using @source, if you point it to a folder, all the same auto source detection rules apply (such as ignoring gitignored files/folders, ignoring binary files and so on).

This means that the moment you add * to the ignored/.gitignore file, the contents is hidden again. This is similar to the node_modules case I mentioned earlier.

The solution here is to be very explicit about the ignored files/folders.

To solve it in your case, you could do this instead:

/* base.css */
@source "ignored/template.html";

This is very explicit and telling Tailwind CSS that it's okay to scan the ignored/template.html file even though it is being ignored.

Does this work for you?

@jhh
Copy link
Author

jhh commented Jan 6, 2025

My repro example was a little contrived and over-simplified. Let me describe what's going on in my actual application and see what you think about its bug-worthiness. It may just be something that could be pointed out in documentation.

I have a base.css that sources a relative path to some third party templates I'm using, thusly:

/* puka/static/puka/base.css */
@source "../../../.venv/lib/python3.12/site-packages/crispy_tailwind";

The @sourced crispy_tailwind folder has 44 files to be scanned for templates:

crispy_tailwind/
.
├── __init__.py
├── layout.py
├── tailwind.py
├── templates
│   └── tailwind
│       ├── display_form.html
│       ├── errors.html
│       ├── errors_formset.html
│       ├── field.html
│       ├── inputs.html
│       ├── layout
│       │   ├── alert.html
│       │   ├── attrs.html
│       │   ├── baseinput.html
│       │   ├── button.html
│       │   ├── buttonholder.html
│       │   ├── checkboxselectmultiple.html
│       │   ├── checkboxselectmultiple_inline.html
│       │   ├── column.html
│       │   ├── div.html
│       │   ├── field_errors.html
│       │   ├── field_errors_block.html
│       │   ├── field_with_buttons.html
│       │   ├── fieldset.html
│       │   ├── formactions.html
│       │   ├── help_text.html
│       │   ├── help_text_and_errors.html
│       │   ├── inline_field.html
│       │   ├── prepended_appended_text.html
│       │   ├── radioselect.html
│       │   ├── radioselect_inline.html
│       │   ├── row.html
│       │   ├── select.html
│       │   └── select_option.html
│       ├── table_inline_formset.html
│       ├── uni_form.html
│       ├── uni_formset.html
│       ├── whole_uni_form.html
│       └── whole_uni_formset.html
└── templatetags
    ├── __init__.py
    ├── tailwind_field.py
    └── tailwind_filters.py

The .venv directory in the path of the @source directive above is created by the uv tool, just like npm creates node_modules. It has over 11k files and is ignored by my project repo.

The problem stems from the fact that uv puts the aforementioned second .gitignore in the root of the .venv directory it creates and hides the template files.

The point you make is a good one, but I'm sourcing template files as explicitly as I can without putting 44 @source directives in my CSS. There's also the confusing fact that I can source when there is a .gitignore 4 or 5 directories above, but not both 4 and 5.

In my case, I deleted the .gitignore that uv created and thankfully it isn't recreating it every time it syncs!

@gregsullivan
Copy link

I'm encountering a variation on this issue unrelated to the @source directive. Because I suspect it is the same fundamental issue with automatic source detection, I figured it would be more helpful to post here.

I use a local development environment powered by Vagrant. The Vagrant configuration is itself a Git repository, and each site is based on a template. This results in a directory structure like this:

Vagrant
├── .git
└── www
     ├── domain.test
     │    └── ...
     └── another-domain.test
          ├── .gitignore [contains `*`]
          └── public_html
               ├── .git
               ├── .gitignore [contains `node_modules`]
               ├── package.json
               ├── tailwind.css
               └── etc.

Each domain folder (domain.test, another-domain.test, etc.) has it's own .gitignore file, and each of those files has * as an entry. (This is meant to protect your work from the site provisioner, which, according to a comment in the file, would otherwise delete your work.)

The result is that automatic source detection fails completely in public_html when public_html is the CWD. (It also fails if the CWD is in any subfolder of public_html, regardless of depth.)

However, if the .gitignore file in the parent folder of the CWD is updated to public_html instead of *, it works again. (public_html/* still fails.)

As an added wrinkle, #15684 applies here, and with the --watch flag, automatic source detection fails on the initial build, but works on subsequent builds while watching. So if one file in public_html is modified, that file's classes will be added, even with * in the .gitignore one level above the CWD.

I have created a minimal reproduction of the above which I'd be happy to share. If you'd prefer I open a separate issue, I can do that as well.

Thanks very much!

@davegaeddert
Copy link

I'm having the same issue with Python/uv as @jhh. The use case is exactly what the example in the docs describes, but for Python. It just doesn't seem to work because of the .gitignore * at the top of .venv, which seems like a bug. If I delete that file and but still have .venv in my repo gitignore, it works just fine.

I don't have a good test case on hand but I'm guessing if you dropped a gitignore * into a node_modules directory, it would break the example too?

Image

@davegaeddert
Copy link

I just tried the latest release and it seems to be working now. @jhh?

@jhh
Copy link
Author

jhh commented Feb 26, 2025

I just tried the latest release and it seems to be working now. @jhh?

I updated to Tailwind v4.0.9 and still see the issue in my test repo.

@davegaeddert
Copy link

Yep, you're right. I was working on a different repo and for some reason it didn't have the .gitignore in the .venv itself.

@davegaeddert
Copy link

davegaeddert commented Feb 26, 2025

Ok, so I think I have some tests for this (of course they don't run in CI — custom runner maybe?): davegaeddert@d35be22

(the first test passes, and the second two fail)

Interestingly to me, using .venv in a root .gitignore is also different from using .venv/**/*. I understand these are technically not the same, but as far as how the thing works, I'm surprised they don't act the same. Anyway, I'm wondering how much of this is just dependent on the behavior of the ignore crate (which has several open issues that may be related).

I wonder about just adding an option to the @source directive to turn off all the gitignore rules for a specific source? That would trickle down into the WalkBuilder and turn off the git rules. Something like:

@source "path" ignore(none);
/*
or @source "path" gitignore(false);
or something...
*/

Then it would be an explicit behavior people are opting in to, and the other uses cases for using @source don't change? @RobinMalfait would that make more sense so we can still point to a directory as a source?

@davegaeddert
Copy link

Or it could be something that ultimately sets builder.parents(false) on WalkBuilder.

@jordankimsey
Copy link

I am facing a similar issue related to the @source directive with Vite based applications. I am using version 4.0.9. I have a custom tailwind design system that I am referencing in my CSS file. In our Next.js / PostCSS applications the @source directive works and all of the tailwind styles are rendered properly. This is not the case for our React Vite based applications.

@njacob1001
Copy link

Same issue here

@brittneypostma
Copy link

I am facing a similar issue related to the @source directive with Vite based applications. I am using version 4.0.9. I have a custom tailwind design system that I am referencing in my CSS file. In our Next.js / PostCSS applications the @source directive works and all of the tailwind styles are rendered properly. This is not the case for our React Vite based applications.

Same issue here with SvelteKit vite based application with a design system imported using the @source directive, no component styles show up.

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

No branches or pull requests

9 participants