Alternative to #16425Fixes#16585Fixes#16389Fixes#16252Fixes#15794Fixes#16646Fixes#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).
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>
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>
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>
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

### Windows prod build after
This includes a debug `console.log(…)` to make sure it matches the right
module.

### Windows dev build after
This includes a debug `console.log(…)` to make sure it matches the right
module.

Closes#15438Closes#15560Closes#15561Closes#15562
This PR upgrades `lightningcss` to `1.29.0` and uses the [new feature
flag](304389600f)
to disable the light-dark function transpilation.
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
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>
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Closes#15159
This PR extends the `@tailwindcss/node` packages to be able to overwrite
the CSS and JS resolvers. This is necessary as some bundlers, in
particular Vite, have a custom module resolution system that can be
individually configured. E.g. in Vite it is possible to add custom
[resolver
configs](https://vite.dev/config/shared-options.html#resolve-conditions)
that is expected to be taken into account.
With the new `customCssResolver` and `customJsResolver` option, we're
able to use the Vite resolvers which take these configs into account.
## Test Plan
Tested in the playground by configuring [resolver
conditions](https://vite.dev/config/shared-options.html#resolve-conditions)
(with Vite 5.4 and Vite 6 beta). An integration test was added for both
the JS and CSS resolvers to ensure it keeps working as expected.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Closes#15160
We need to set browser targets for each browser individually to see
vendor prefixes created for each browser.
Exact values are up for discussion, this first pass is taken from
@adamwathan's comments in
https://github.com/tailwindlabs/tailwindcss/issues/15160
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Closes#14965
This PR changes the way we register Tailwind CSS as a Svelte
preprocessor when using the Vite plugin. The idea is to reduce the
bookkeeping for interacting with CSS inside `<style>` tags so that we
have a more consistent behavior and make sure the Svelte-specific
post-processing (e.g. local class mangling) works as expected.
Prior to this change, we were running Tailwind CSS as a Svelte
preprocessor and then we would transform the file again when necessary
inside the Vite `transform` hook. This is necessary to have the right
list of candidates when we build the final CSS, but it did cause some
situation to not apply the Svelte post-processors anymore. The repro for
this seemed to indicate a timing specific issue and I did notice that
specifically the code where we invalidate modules in Vite would cause
unexpected processing orders.
We do, however, not officially support rendering utilities (`@tailwind
utilities;`) inside `<style>` tag. This is because the `<style>` block
is scoped by default and emitting utilities will always include
utilities for all classes in your whole project. For this case, we
highly recommend creating as separate `.css` file and importing it
explicitly.
With this limitation in place, the additional bookkeeping where we need
to invalidate modules because the candidate list has changed is no
longer necessary and removing it allows us to reduce the complexity of
the Svelte integration.
## Test Plan
https://github.com/user-attachments/assets/32c8e91f-ab21-48c6-aeaf-2582273b9bac
Not seen in the test plan above I also tested the `pnpm build --watch`
step of the Vite project. This does require the `pnpm preview` server to
restart but the build artifact are updated as expected.
Closes#13305
This PR adds registers a Svelte preprocessor, used by the Svelte Vite
plugin, to run Tailwind CSS for styles inside the `<style>` block, this
enables users to use Tailwind CSS features like `@apply` from inside
Svelte components:
```svelte
<script>
let name = 'world'
</script>
<h1 class="foo underline">Hello {name}!</h1>
<style global>
@import 'tailwindcss/utilities';
@import 'tailwindcss/theme' theme(reference);
@import './components.css';
</style>
```
## Test Plan
I've added integration tests to validate this works as expected.
Furthermore I've used the
[tailwindcss-playgrounds](https://github.com/philipp-spiess/tailwind-playgrounds)
SvelteKit project to ensure this works in an end-to-end setup:
<img width="2250" alt="Screenshot 2024-11-08 at 14 45 31"
src="https://github.com/user-attachments/assets/64e9e0f3-53fb-4039-b0a7-3ce945a29179">
Fixes: #14839Fixes: #14796
This PR fixes an issue in the Vite extension where we previously only
ran a small list of allow-listed plugins for the second stage transform
in the build step. This caused some CSS features to unexpectedly not
work in production builds (one such example is Vue's `:deep(...)`
selector).
To fix this, I changed the allow listed plugins that we do want to run
to a block list to filter out some plugins we know we don't want to run
(e.g. the Tailwind Vite plugin for example or some built-in Vite plugins
that are not necessary).
## Test plan
This PR adds a new integration test suite to test interop with a custom
Vite transformer that looks like this:
```js
{
name: 'recolor',
transform(code, id) {
if (id.includes('.css')) {
return code.replace(/red/g, 'blue')
}
},
}
```
I also validated that this does indeed fix the Vue `:deep(...)` selector
related issue that we were seeing by copying the repro of #14839 into
our playground:

You can see in the screenshot above that the `:deep()` selector
overwrites the scoped styles as expected in both the dev mode and the
prod build (screenshotted).
Furthermore I reproduced the issue reported in
https://github.com/tailwindlabs/tailwindcss/issues/14796 and was able to
confirm that in a production build, the styling works as expected:
<img width="517" alt="Screenshot 2024-11-06 at 14 26 50"
src="https://github.com/user-attachments/assets/ade6fe38-be0d-4bd0-9a9a-67b6fec05ae0">
Lastly, I created a repository out of the biggest known-to-me Vite
projects: [Astro, Nuxt, Remix, SolidStart, and
SvelteKit](https://github.com/philipp-spiess/tailwind-playgrounds) and
verified that both dev and prod builds show no issue and the candidate
list is properly appended in each case.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Fixes#14784
This is an alternative to #14850 in which we actually perform url
rewriting / rebasing ourselves. We ported a large portion of the
URL-rewriting code from Vite (with attribution) to use here with some
minor modifications. We've added test cases for the url rewriting so
verifying individual cases is easy. We also wrote integration tests for
Vite that use PostCSS and Lightning CSS that verify that files are found
and inlined or relocated/renamed as necessary.
We also did some manual testing in the Playground to verify that this
works as expected across several CSS files and directories which you can
see a screenshot from here:
<img width="1344" alt="Screenshot 2024-11-05 at 10 25 16"
src="https://github.com/user-attachments/assets/ff0b3ac8-cdc9-4e26-af79-36396a5b77b9">
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
This PR improves the generated CSS by running it through Lightning CSS
twice.Right now Lightning CSS merges adjacent at-rules and at the end
flattens the nesting. This means that after the nesting is flattened,
the at-rules that are adjacent and could be merged together will not be
merged.
This PR improves our output by running Lightning CSS twice on the
generated CSS which will make sure to merge adjacent at-rules after the
nesting is flattened.
Note: in the diff output you'll notice that some properties are
duplicated. These need some fixes in Lightning CSS itself but they don't
break anything for us right now.
Related PR in Lightning CSS for the double `-webkit-backdrop-filter` can
be found here: https://github.com/parcel-bundler/lightningcss/pull/850
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
When migrating a project from Tailwind CSS v3 to Tailwind CSS v4, then
we started the migration process in the following order:
1. Migrate the JS/TS config file
2. Migrate the source files (found via the `content` option)
3. Migrate the CSS files
However, if you have a setup where you have multiple CSS root files
(e.g.: `frontend` and `admin` are separated), then that typically means
that you have an `@config` directive in your CSS files. These point to
the Tailwind CSS config file.
This PR changes the migration order to do the following:
1. Build a tree of all the CSS files
2. For each `@config` directive, migrate the JS/TS config file
3. For each JS/TS config file, migrate the source files
If a CSS file does not contain any `@config` directives, then we start
by filling in the `@config` directive with the default Tailwind CSS
config file (if found, or the one passed in). If no default config file
or passed in config file can be found, then we will error out (just like
we do now)
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This PR introduces a new `source(…)` argument and improves on the
existing `@source`. The goal of this PR is to make the automatic source
detection configurable, let's dig in.
By default, we will perform automatic source detection starting at the
current working directory. Auto source detection will find plain text
files (no binaries, images, ...) and will ignore git-ignored files.
If you want to start from a different directory, you can use the new
`source(…)` next to the `@import "tailwindcss/utilities"
layer(utilities) source(…)`.
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss/utilities' layer(utilities) source('../../');
```
Most people won't split their source files, and will just use the simple
`@import "tailwindcss";`, because of this reason, you can use
`source(…)` on the import as well:
E.g.:
```css
/* ./src/styles/index.css */
@import 'tailwindcss' source('../../');
```
Sometimes, you want to rely on auto source detection, but also want to
look in another directory for source files. In this case, yuo can use
the `@source` directive:
```css
/* ./src/index.css */
@import 'tailwindcss';
/* Look for `blade.php` files in `../resources/views` */
@source '../resources/views/**/*.blade.php';
```
However, you don't need to specify the extension, instead you can just
point the directory and all the same automatic source detection rules
will apply.
```css
/* ./src/index.css */
@import 'tailwindcss';
@source '../resources/views';
```
If, for whatever reason, you want to disable the default source
detection feature entirely, and only want to rely on very specific glob
patterns you define, then you can disable it via `source(none)`.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Only look at .blade.php files, nothing else */
@source "../resources/views/**/*.blade.php";
```
Note: even with `source(none)`, if your `@source` points to a directory,
then auto source detection will still be performed in that directory. If
you don't want that, then you can simply add explicit files in the globs
as seen in the previous example.
```css
/* Completely disable the default auto source detection */
@import 'tailwindcss' source(none);
/* Run auto source detection in `../resources/views` */
@source "../resources/views";
```
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Fixes: #14558
This PR fixes an issue where our Vite plugin would crash when trying to load stylesheets via certain static asset query parameters:
```ts
import raw from './style.css?raw'
import url from './style.css?url'
```
The proper behavior for our extension is to _not touch these file at all_. The `?raw` identifier should never transform anything and the `?url` one will emit a module which points to the asset URL. However, if that URL is loaded as a stylesheet, another transform hook is called and the file is properly transformed. I verified this in the Vite setup and have added an integration test ensuring these two features work as expected.
I've also greatly reduced the complexity of the Vite playground to make it easier to set up examples like this in the future.
This PR improves the performance of the `@tailwindcss/postcss` plugin.
Before this change we created 2 compiler instances instead of a single
one. On a project where a `tailwindcss.config.ts` file is used, this
means that the timings look like this:
```
[@tailwindcss/postcss] Setup compiler: 137.525ms
⋮
[@tailwindcss/postcss] Setup compiler: 43.95ms
```
This means that with this small change, we can easily shave of ~50ms for
initial PostCSS builds.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR exposes when using the the `DEBUG` environment variable. This
follows the `DEBUG` conventions where:
- `DEBUG=1`
- `DEBUG=true`
- `DEBUG=*`
- `DEBUG=tailwindcss`
Will enable the debug information, but when using:
- `DEBUG=0`
- `DEBUG=false`
- `DEBUG=-tailwindcss`
It will not.
This currently only exposes some timings related to:
1. Scanning for candidates
2. Building the CSS
3. Optimizing the CSS
We can implement a more advanced version of this where we also expose
more fine grained information such as the files we scanned, the amount
of candidates we found and so on. But I believe that this will be enough
to start triaging performance related issues.
This PR brings `@import` resolution into Tailwind CSS core. This means
that our clients (PostCSS, Vite, and CLI) no longer need to depend on
`postcss` and `postcss-import` to resolve `@import`. Furthermore this
simplifies the handling of relative paths for `@source`, `@plugin`, or
`@config` in transitive CSS files (where the relative root should always
be relative to the CSS file that contains the directive). This PR also
fixes a plugin resolution bug where non-relative imports (e.g. directly
importing node modules like `@plugin '@tailwindcss/typography';`) would
not work in CSS files that are based in a different npm package.
### Resolving `@import`
The core of the `@import` resolution is inside
`packages/tailwindcss/src/at-import.ts`. There, to keep things
performant, we do a two-step process to resolve imports. Imagine the
following input CSS file:
```css
@import "tailwindcss/theme.css";
@import "tailwindcss/utilities.css";
```
Since our AST walks are synchronous, we will do a first traversal where
we start a loading request for each `@import` directive. Once all loads
are started, we will await the promise and do a second walk where we
actually replace the AST nodes with their resolved stylesheets. All of
this is recursive, so that `@import`-ed files can again `@import` other
files.
The core `@import` resolver also includes extensive test cases for
[various combinations of media query and supports conditionals as well
als layered
imports](https://developer.mozilla.org/en-US/docs/Web/CSS/@import).
When the same file is imported multiple times, the AST nodes are
duplicated but duplicate I/O is avoided on a per-file basis, so this
will only load one file, but include the `@theme` rules twice:
```css
@import "tailwindcss/theme.css";
@import "tailwindcss/theme.css";
```
### Adding a new `context` node to the AST
One limitation we had when working with the `postcss-import` plugin was
the need to do an additional traversal to rewrite relative `@source`,
`@plugin`, and `@config` directives. This was needed because we want
these paths to be relative to the CSS file that defines the directive
but when flattening a CSS file, this information is no longer part of
the stringifed CSS representation. We worked around this by rewriting
the content of these directives to be relative to the input CSS file,
which resulted in added complexity and caused a lot of issues with
Windows paths in the beginning.
Now that we are doing the `@import` resolution in core, we can use a
different data structure to persist this information. This PR adds a new
`context` node so that we can store arbitrary context like this inside
the Ast directly. This allows us to share information with the sub tree
_while doing the Ast walk_.
Here's an example of how the new `context` node can be used to share
information with subtrees:
```ts
const ast = [
rule('.foo', [decl('color', 'red')]),
context({ value: 'a' }, [
rule('.bar', [
decl('color', 'blue'),
context({ value: 'b' }, [
rule('.baz', [decl('color', 'green')]),
]),
]),
]),
]
walk(ast, (node, { context }) => {
if (node.kind !== 'declaration') return
switch (node.value) {
case 'red': assert(context.value === undefined)
case 'blue': assert(context.value === 'a')
case 'green': assert(context.value === 'b')
}
})
```
In core, we use this new Ast node specifically to persist the `base`
path of the current CSS file. We put the input CSS file `base` at the
root of the Ast and then overwrite the `base` on every `@import`
substitution.
### Removing the dependency on `postcss-import`
Now that we support `@import` resolution in core, our clients no longer
need a dependency on `postcss-import`. Furthermore, most dependencies
also don't need to know about `postcss` at all anymore (except the
PostCSS client, of course!).
This also means that our workaround for rewriting `@source`, the
`postcss-fix-relative-paths` plugin, can now go away as a shared
dependency between all of our clients. Note that we still have it for
the PostCSS plugin only, where it's possible that users already have
`postcss-import` running _before_ the `@tailwindcss/postcss` plugin.
Here's an example of the changes to the dependencies for our Vite client
✨ :
<img width="854" alt="Screenshot 2024-09-19 at 16 59 45"
src="https://github.com/user-attachments/assets/ae1f9d5f-d93a-4de9-9244-61af3aff1237">
### Performance
Since our Vite and CLI clients now no longer need to use `postcss` at
all, we have also measured a significant improvement to the initial
build times. For a small test setup that contains only a hand full of
files (nothing super-complex), we measured an improvement in the
**3.5x** range:
<img width="1334" alt="Screenshot 2024-09-19 at 14 52 49"
src="https://github.com/user-attachments/assets/06071fb0-7f2a-4de6-8ec8-f202d2cc78e5">
The code for this is in the commit history if you want to reproduce the
results. The test was based on the Vite client.
### Caveats
One thing to note is that we previously relied on finding specific
symbols in the input CSS to _bail out of Tailwind processing
completely_. E.g. if a file does not contain a `@tailwind` or `@apply`
directive, it can never be a Tailwind file.
Since we no longer have a string representation of the flattened CSS
file, we can no longer do this check. However, the current
implementation was already inconsistent with differences on the allowed
symbol list between our clients. Ideally, Tailwind CSS should figure out
wether a CSS file is a Tailwind CSS file. This, however, is left as an
improvement for a future API since it goes hand-in-hand with our planned
API changes for the core `tailwindcss` package.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This works similar to the Vue setup. The styles that Astro will receive
might still contain Tailwind CSS APIs but since it's not picky, we can
pass that through to the regular Vite `transform` handlers for now.
This, however, will have issues like
https://github.com/tailwindlabs/tailwindcss/issues/14205. We have to fix
this together with Vue and other similar extensions later. For now, it
will break when syntax is used that lightningcss rewrites (like `@apply
text-3xl/tight;`)
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
We noticed that Nuxt projects were not working with the tailwindcss
project. The issue was traced down to the fact that Nuxt starts multiple
Vite dev servers and calling the experimental `waitForRequestsIdle()` on
one of the test servers would never resolve.
This was fixed upstream and is part of the latest Vite/Nuxt release:
https://github.com/vitejs/vite/issues/17980.
We still need to handle the fact that Vite can spawn multiple dev
servers. This is necessary because when we invalidate all roots, we need
to find that module inside all of the spawned servers. If we only look
at the _last server_ (what we have done before), we would not find the
module and thus could not invalidate it.
Fixes#14205Fixes#14106
This PR reworks the Vite extension in order to supprt `lightningcss` as
the pre-processor, enable faster rebuilds, and adds support for `vite
build --watch` mode. To make this change possible, we've done two major
changes to the extension that have caused the other changes.
## 1. Tailwind CSS is a preprocessor
We now run all of our modifications in `enforce: 'pre'`. This means that
Tailwind CSS now gets the untransformed CSS files rather than the one
already going through postcss or lightningcss. We do this because
Tailwind CSS _is_ a preprocessor at the same level as those tools and we
do sometimes use the language in ways that [creates problems when it's
the input for other
bundlers](https://github.com/tailwindlabs/tailwindcss/pull/14269).
The correct solution here is to make Tailwind not depend on any other
transformations. The main reason we were not using the `enforce: 'pre'`
phase in Vite before was becuase we relied on the `@import` flattening
of postcss so we now have to do this manually. `@import` flattening is
now a concern that every Tailwind V4 client has to deal with so this
might actually be something we want to inline into tailwindcss in the
future.
## 2. A Vite config can have multiple Tailwind roots
This is something that we have not made very explicit in the previous
iteration of the Vite plugin but we have to support multiple Tailwind
roots in a single Vite workspace. A Tailwind root is a CSS file that is
used to configure Tailwind. Technically, any CSS file can be the input
for `tailwindcss` but you have to add certain rules (e.g. `@import
"tailwindcss";`) to make the compiler do something.
A workspace can have multiple of these rules (e.g. by having different
Tailwind configures for different sub-pages). With the addition of
[support for `@source`
rules](https://github.com/tailwindlabs/tailwindcss/pull/14078) and [JS
config files](https://github.com/tailwindlabs/tailwindcss/pull/14239),
Tailwind roots become more complex and can have a custom list of
_dependencies_ (that is other JavaScript modules the compiler includes
as part of these new rules). In order to _only rebuild the roots we need
to_, we have to make this separation very clear.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
When you configure custom content globs inside an `@config` file, we
want to tread these globs as being relative to that config file and not
the CSS file that requires the content file. A config can be used by
multiple CSS configs.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
In Tailwind v4 the CSS file is the main entry point to your project and
is generally configured via `@theme`. However, given that all v3
projects were configured via a `tailwind.config.js` file we definitely
need to support those. This PR adds support for loading existing
Tailwind config files by adding an `@config` directive to the CSS —
similar to how v3 supported multiple config files except that this is
now _required_ to use a config file.
You can load a config file like so:
```
@import "tailwindcss";
@config "./path/to/tailwind.config.js";
```
A few notes:
- Both CommonJS and ESM config files are supported (loaded directly via
`import()` in Node)
- This is not yet supported in Intellisense or Prettier — should
hopefully land next week
- TypeScript is **not yet** supported in the config file — this will be
handled in a future PR.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
This PR fixes an issue introduced with the changed candidate cache
behavior in #14187.
Prior to #14187, candidates were cached globally within an instance of
Oxide. This meant that once a candidate was discovered, it would not
reset until you either manually cleared the cache or restarted the Oxide
process. With the changes in #14187 however, the cache was scoped to the
instance of the `Scanner` class with the intention of making the caching
behavior more easy to understand and to avoid a global cache.
This, however, had an unforeseen side-effect in our Vite extension.
Vite, in dev mode, discovers files _lazily_. So when a developer goes to
`/index.html` the first time, we will scan the `/index.html` file for
Tailwind candidates and then build a CSS file with those candidate. When
they go to `/about.html` later, we will _append_ the candidates from the
new file and so forth.
The problem now arises when the dev server detects changes to the input
CSS file. This requires us to do a re-scan of that CSS file which, after
#14187, caused the candidate cache to be gone. This is usually fine
since we would just scan files again for the changed candidate list but
in the Vite case we would only get the input CSS file change _but no
subsequent change events for all other files, including those currently
rendered in the browser_). This caused updates to the CSS file to remove
all candidates from the CSS file again.
Ideally, we can separate between two concepts: The candidate cache and
the CSS input file scan. An instance of the `Scanner` could re-parse the
input CSS file without having to throw away previous candidates. This,
however, would have another issue with the current Vite extension where
we do not properly retain instances of the `Scanner` class anyways. To
properly improve the cache behavior, we will have to fix the Vite
`Scanner` retaining behavior first. Unfortunately this means that for
the short term, we have to add some manual bookkeeping to the Vite
client and retain the candidate cache between builds ourselves.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR updates the API for interacting with the Oxide API. Until now,
we used the name `scanDir(…)` which is fine, but we do way more work
right now.
We now have features such as:
1. Auto source detection (can be turned off, e.g.: `@tailwindcss/vite`
doesn't need it)
2. Scan based on `@source`s found in CSS files
3. Do "incremental" rebuilds (which means that the `scanDir(…)` result
was stateful).
To solve these issues, this PR introduces a new `Scanner` class where
you can pass in the `detectSources` and `sources` options. E.g.:
```ts
let scanner = new Scanner({
// Optional, omitting `detectSources` field disables automatic source detection
detectSources: { base: __dirname },
// List of glob entries to scan. These come from `@source` directives in CSS.
sources: [
{ base: __dirname, pattern: "src/**/*.css" },
// …
],
});
```
The scanner object has the following API:
```ts
export interface ChangedContent {
/** File path to the changed file */
file?: string
/** Contents of the changed file */
content?: string
/** File extension */
extension: string
}
export interface DetectSources {
/** Base path to start scanning from */
base: string
}
export interface GlobEntry {
/** Base path of the glob */
base: string
/** Glob pattern */
pattern: string
}
export interface ScannerOptions {
/** Automatically detect sources in the base path */
detectSources?: DetectSources
/** Glob sources */
sources?: Array<GlobEntry>
}
export declare class Scanner {
constructor(opts: ScannerOptions)
scan(): Array<string>
scanFiles(input: Array<ChangedContent>): Array<string>
get files(): Array<string>
get globs(): Array<GlobEntry>
}
```
The `scanFiles(…)` method is used for incremental rebuilds. It takes the
`ChangedContent` array for all the new/changes files. It returns whether
we scanned any new candidates or not.
Note that the `scanner` object is stateful, this means that we don't
have to track candidates in a `Set` anymore. We can just call
`getCandidates()` when we need it.
This PR also removed some unused code that we had in the `scanDir(…)`
function to allow for sequential or parallel `IO`, and sequential or
parallel `Parsing`. We only used the same `IO` and `Parsing` strategies
for all files, so I just got rid of it.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR adds support to transforming `<style>` blocks emitted by Vue
components with tailwindcss when the `@tailwindcss/vite` is used.
Example:
```vue
<style>
@import 'tailwindcss/utilities';
@import 'tailwindcss/theme' theme(reference);
.foo {
@apply text-red-500;
}
</style>
<template>
<div class="underline foo">Hello Vue!</div>
</template>
```
Additionally, this PR also adds an integration test.
---------
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Alternative to #14110
This PR changes the way how we load plugins to be compatible with ES6
async `import`s. This allows us to load plugins even inside the browser
but it comes at a downside: We now have to change the `compile` API to
return a `Promise`...
So most of this PR is rewriting all of the call sites of `compile` to
expect a promise instead of the object.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
This PR is an umbrella PR where we will add support for the new
`@source` directive. This will allow you to add explicit content glob
patterns if you want to look for Tailwind classes in other files that
are not automatically detected yet.
Right now this is an addition to the existing auto content detection
that is automatically enabled in the `@tailwindcss/postcss` and
`@tailwindcss/cli` packages. The `@tailwindcss/vite` package doesn't use
the auto content detection, but uses the module graph instead.
From an API perspective there is not a lot going on. There are only a
few things that you have to know when using the `@source` directive, and
you probably already know the rules:
1. You can use multiple `@source` directives if you want.
2. The `@source` accepts a glob pattern so that you can match multiple
files at once
3. The pattern is relative to the current file you are in
4. The pattern includes all files it is matching, even git ignored files
1. The motivation for this is so that you can explicitly point to a
`node_modules` folder if you want to look at `node_modules` for whatever
reason.
6. Right now we don't support negative globs (starting with a `!`) yet,
that will be available in the near future.
Usage example:
```css
/* ./src/input.css */
@import "tailwindcss";
@source "../laravel/resources/views/**/*.blade.php";
@source "../../packages/monorepo-package/**/*.js";
```
It looks like the PR introduced a lot of changes, but this is a side
effect of all the other plumbing work we had to do to make this work.
For example:
1. We added dedicated integration tests that run on Linux and Windows in
CI (just to make sure that all the `path` logic is correct)
2. We Have to make sure that the glob patterns are always correct even
if you are using `@import` in your CSS and use `@source` in an imported
file. This is because we receive the flattened CSS contents where all
`@import`s are inlined.
3. We have to make sure that we also listen for changes in the files
that match any of these patterns and trigger a rebuild.
PRs:
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14063
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14085
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14079
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14067
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14076
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14080
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14127
- [x] https://github.com/tailwindlabs/tailwindcss/pull/14135
Once all the PRs are merged, then this umbrella PR can be merged.
> [!IMPORTANT]
> Make sure to merge this without rebasing such that each individual PR
ends up on the main branch.
---------
Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
* Add basic `addVariant` plugin support
* Return early
* Load plugins right away instead later
* Use correct type for variant name
* Preliminary support for addVariant plugins in PostCSS plugin
* Add test for compounding plugin variants
* Add basic `loadPlugin` support to Vite plugin
* Add basic `loadPlugin` support to CLI
* add `io.ts` for integrations
* use shared `loadPlugin` from `tailwindcss/io`
* add `tailwindcss-test-utils` to `@tailwindcss/cli` and `@tailwindcss/vite`
* only add `tailwindcss-test-utils` to `tailwindcss` as a dev dependency
Because `src/io.ts` is requiring the plugin.
* move `tailwindcss-test-utils` to `@tailwindcss/postcss `
This is the spot where we actually need it.
* use newer pnpm version
* Duplicate loadPlugin implementation instead of exporting io file
* Remove another io reference
* update changelog
---------
Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
* ensure we wait in the `build` step as well
It looks like when running `astro build` we only run this `build` step
and not the `dev` step where we already use the `waitForRequestsIdle`
code.
Adding this to the `build` part as well does generate the correct
result.
* update changelog
* fix typo
* add comment
* Don’t run transforms more than necessary
* Don’t remove modules from the graph during SSR
* Update changelog
* Add `preview` script
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
Transform Tailwind-generated CSS with Vite CSS plugins. vite:css does useful things like transforming url() paths and inlining images. vite:css-post generates bundle hashes. Before this change, the CSS bundle hash wasn't changing when the generated CSS changed.
Also adds Vite 5.2 peerDependancy.
---------
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
* add `@tailwindcss/optimize` as a separate package
* remove lightningcss from `tailwindcss`
* import `optimizeCss` from `@tailwindcss/optimize`
* ensure we use `src/` files in development
* move `devDependencies` after `dependencies`
Just for consistency
* inline `optimizeCss` in leaf packages
Instead of introducing a custom `@tailwindcss/optimize` package
* update changelog
* fix changelog
* ensure we don't crash on deleted files
* change return type of `compile` to include a `rebuild()` function
This will allow us in the future to perform incremental rebuilds after
the initial rebuild. This is purely the API change so that we can
prepare all the call sites to use this new API.
* set `@tailwind utilities` nodes
Instead of replacing the node that represents the `@tailwind utilities`
with the generated AST nodes from the rawCandidates, we will set the
nodes of the `@tailwind utilities` rule to the AST nodes instead.
This way we dont' have to remove and replace the `@tailwind utilities`
rule with `n` new nodes. This will later allow us to track the
`@tailwindcss utilities` rule itself and update its `nodes` for
incremental rebuilds.
This also requires a small change to the printer where we now need to
print the children of the `@tailwind utilities` rule. Note: we keep the
same `depth` as-if the `@tailwindcss utilities` rule was not there.
Otherwise additional indentation would be present.
* move sorting to the `ast.sort()` call
This will allow us to keep sorting AST nodes in a single spot.
* move parser functions to the `DesignSystem`
This allows us to put all the parsers in the `DesignSystem`, this allows
us to scope the parsers to the current design system (the current theme,
current utility values and variants).
The implementation of these parsers are also using a `DefaultMap`
implementation. This allows us to make use of caching and only parse a
candidate, parse a variant or compile AST nodes for a given raw
candidate once if we've already done this work in the past.
Again, this is scoped to the `DesignSystem` itself. This means that if
the corresponding theme changes, then we will create a new
`DesignSystem` entirely and the caches will be garbage collected. This
is important because a candidate like `bg-primary` can be invalid in
`DesignSystem` A, but can be valid in `DesignSystem` B and vice versa.
* ensure we sort variants alphabetically by root
For incremental rebuilds we don't know all the used variants upfront,
which means that we can't sort them upfront either (what we used to do).
This code now allows us to sort the variants deterministically when
sorting the variants themselves instead of relying on the fact that they
used to be sorted before.
The sort itself could change slightly compared to the previous
implementation (especially when you used stacked variants in your
candidates), but it will still be deterministic.
* replace `localeCompare` comparisons
Use cheaper comparisons than `localeCompare` when comparing 2 strings.
We currently don't care if it is 100% correctly sorted, but we just want
consistent sorting. This is currently faster compared to
`localeCompare`.
Another benefit is that `localeCompare` could result in
non-deterministic results if the CSS was generated on 2 separate
computers where the `locale` is different.
We could solve that by adding a dedicated locale, but it would still be
slower compared to this.
* track invalid candidates
When an incoming raw candidates doesn't produce any output, then we can
mark it as an invalid candidate. This will allow us to reduce the amount
of candidates to handle in incremental rebuilds.
* add initial incremental rebuild implementation
This includes a number of steps:
1. Track the `@tailwind utilities` rule, so that we can adjust its nodes
later without re-parsing the full incoming CSS.
2. Add the new incoming raw candidates to the existing set of
candidates.
3. Parse the merged set to `compileCandidates` (this can accept any
`Iterable<string>`, which means `string[]`, `Set<string>`, ...)
4. Get the new AST nodes, update the `@tailwind utilities` rule's nodes
and re-print the AST to CSS.
* improvement 1: ignore known invalid candidates
This will reduce the amount of candidates to handle. They would
eventually be skipped anyway, but now we don't even have to re-parse
(and hit a cache) at all.
* improvement 2: skip work, when generated AST is the same
Currently incremental rebuilds are additive, which means that we are not
keeping track if we should remove CSS again in development.
We can exploit this information, because now we can quickly check the
amoutn of generated AST nodes.
- If they are the same then nothing new is generated — this means that
we can re-use the previous compiled CSS. We don't even have to
re-print the AST because we already did do that work in the past.
- If there are more AST nodes, something new is generated — this means
that we should update the `@tailwind utilities` rule and re-print the
CSS. We can store the result for future incremental rebuilds.
* improvement 3: skip work if no new candidates are detected
- We already know a set of candidates from previous runs.
- We also already know a set of candidates that are invalid and don't
produce anything.
This means that an incremental rebuild could give us a new set of
candidates that either already exist or are invalid.
If nothing changes, then we can re-use the compiled CSS.
This actually happens more often than you think, and the bigger your
project is the better this optimization will be.
For example:
```
// Imagine file A exists:
<div class="flex items-center justify-center"></div>
<button class="text-red-500">Delete</button>
```
```
// Now you add a second file B:
<div class="text-red-500 flex"></div>
```
You just created a brand new file with a bunch of HTML elements and
classes, yet all of the candidates in file B already exist in file A, so
nothing changes to the actual generated CSS.
Now imagine the other hundreds of files that already contain hundreds of
classes.
The beauty of this optimization is two-fold:
- On small projects, compiling is very fast even without this check.
This means it is performant.
- On bigger projects, we will be able to re-use existing candidates.
This means it stays performant.
* remove `getAstNodeSize`
We can move this up the tree and move it to the `rebuild` function
instead.
* remove invalid candidate tracking from `DesignSystem`
This isn't used anywhere but only in the `rebuild` of the compile step.
This allows us to remove it entirely from core logic, and move it up the
chain where it is needed.
* replace `throwOnInvalidCandidate` with `onInvalidCanidate`
This was only needed for working with `@apply`, now this logic _only_
exists in the code path where we are handling `@apply`.
* update `compile` API signature
* update callsite of `compile()` function
* fix typo
* Vite plugin: stop checking CSS files that no longer exist
Fix a potential memory leak in the Vite plugin. If CSS files are created that include `@tailwind` and later deleted, remove them from the list of files checked.
Thanks to @timneutkens for highlighting this issue.
* Vite plugin: don't log when files are removed
* only run Lightning CSS when passing `--minify` to the CLI
* only optimize the CSS when creating a production build
* add `optimize` option to PostCSS plugin
- The optimize option can be set to `true`, which will optimize
(unnesting, adding browser prefixes, lowering values) and minify
- The optimize option can also be set to `{ minify: false }`, which will
optimize but not minify.
* default `optimize` option to the true for `NODE_ENV=production`
* add `--optimize` flag to CLI
This will only optimize the CSS output without minification.
* update `--minify` description
* update changelog