Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
import dedent from 'dedent'
|
2024-09-02 15:23:46 +02:00
|
|
|
import os from 'node:os'
|
2024-08-07 16:38:44 +02:00
|
|
|
import path from 'node:path'
|
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
|
|
|
import { describe } from 'vitest'
|
2024-11-19 18:39:49 +01:00
|
|
|
import { candidate, css, html, js, json, test, ts, yaml } from '../utils'
|
2024-08-07 16:38:44 +02:00
|
|
|
|
2024-09-02 15:23:46 +02:00
|
|
|
const STANDALONE_BINARY = (() => {
|
|
|
|
|
switch (os.platform()) {
|
|
|
|
|
case 'win32':
|
|
|
|
|
return 'tailwindcss-windows-x64.exe'
|
|
|
|
|
case 'darwin':
|
|
|
|
|
return os.arch() === 'x64' ? 'tailwindcss-macos-x64' : 'tailwindcss-macos-arm64'
|
|
|
|
|
case 'linux':
|
|
|
|
|
return os.arch() === 'x64' ? 'tailwindcss-linux-x64' : 'tailwindcss-linux-arm64'
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(`Unsupported platform: ${os.platform()} ${os.arch()}`)
|
|
|
|
|
}
|
|
|
|
|
})()
|
|
|
|
|
|
|
|
|
|
describe.each([
|
|
|
|
|
['CLI', 'pnpm tailwindcss'],
|
|
|
|
|
[
|
|
|
|
|
'Standalone CLI',
|
|
|
|
|
path.resolve(__dirname, `../../packages/@tailwindcss-standalone/dist/${STANDALONE_BINARY}`),
|
|
|
|
|
],
|
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
|
|
|
])('%s', (kind, command) => {
|
2024-09-02 15:23:46 +02:00
|
|
|
test(
|
|
|
|
|
'production build',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`{}`,
|
|
|
|
|
'pnpm-workspace.yaml': yaml`
|
|
|
|
|
#
|
|
|
|
|
packages:
|
|
|
|
|
- project-a
|
|
|
|
|
`,
|
|
|
|
|
'project-a/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'project-a/index.html': html`
|
|
|
|
|
<div
|
2024-11-07 16:48:49 +01:00
|
|
|
class="underline 2xl:font-bold hocus:underline inverted:flex *:flex **:flex"
|
2024-09-02 15:23:46 +02:00
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
'project-a/plugin.js': js`
|
|
|
|
|
module.exports = function ({ addVariant }) {
|
|
|
|
|
addVariant('inverted', '@media (inverted-colors: inverted)')
|
|
|
|
|
addVariant('hocus', ['&:focus', '&:hover'])
|
2024-08-07 16:38:44 +02:00
|
|
|
}
|
2024-09-02 15:23:46 +02:00
|
|
|
`,
|
2024-09-03 16:54:08 +02:00
|
|
|
'project-a/tailwind.config.js': js`
|
|
|
|
|
module.exports = {
|
|
|
|
|
content: ['../project-b/src/**/*.js'],
|
|
|
|
|
}
|
|
|
|
|
`,
|
2024-09-02 15:23:46 +02:00
|
|
|
'project-a/src/index.css': css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
2024-09-03 16:54:08 +02:00
|
|
|
@config '../tailwind.config.js';
|
|
|
|
|
@source '../../project-b/src/**/*.html';
|
2024-09-02 15:23:46 +02:00
|
|
|
@plugin '../plugin.js';
|
|
|
|
|
`,
|
|
|
|
|
'project-a/src/index.js': js`
|
|
|
|
|
const className = "content-['project-a/src/index.js']"
|
|
|
|
|
module.exports = { className }
|
|
|
|
|
`,
|
2024-09-03 16:54:08 +02:00
|
|
|
'project-b/src/index.html': html`
|
|
|
|
|
<div class="flex" />
|
|
|
|
|
`,
|
2024-09-02 15:23:46 +02:00
|
|
|
'project-b/src/index.js': js`
|
|
|
|
|
const className = "content-['project-b/src/index.js']"
|
|
|
|
|
module.exports = { className }
|
|
|
|
|
`,
|
|
|
|
|
},
|
2024-08-07 16:38:44 +02:00
|
|
|
},
|
2024-09-02 15:23:46 +02:00
|
|
|
async ({ root, fs, exec }) => {
|
|
|
|
|
await exec(`${command} --input src/index.css --output dist/out.css`, {
|
|
|
|
|
cwd: path.join(root, 'project-a'),
|
|
|
|
|
})
|
2024-08-07 16:38:44 +02:00
|
|
|
|
2024-09-02 15:23:46 +02:00
|
|
|
await fs.expectFileToContain('project-a/dist/out.css', [
|
|
|
|
|
candidate`underline`,
|
2024-09-03 16:54:08 +02:00
|
|
|
candidate`flex`,
|
2024-09-02 15:23:46 +02:00
|
|
|
candidate`content-['project-a/src/index.js']`,
|
|
|
|
|
candidate`content-['project-b/src/index.js']`,
|
|
|
|
|
candidate`inverted:flex`,
|
|
|
|
|
candidate`hocus:underline`,
|
2024-11-07 16:48:49 +01:00
|
|
|
candidate`*:flex`,
|
|
|
|
|
candidate`**:flex`,
|
2024-09-02 15:23:46 +02:00
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
)
|
2024-08-07 16:38:44 +02:00
|
|
|
|
2025-01-17 16:23:34 +01:00
|
|
|
test(
|
|
|
|
|
'production build — read input from stdin',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`{}`,
|
|
|
|
|
'pnpm-workspace.yaml': yaml`
|
|
|
|
|
#
|
|
|
|
|
packages:
|
|
|
|
|
- project-a
|
|
|
|
|
`,
|
|
|
|
|
'project-a/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'project-a/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="underline 2xl:font-bold hocus:underline inverted:flex *:flex **:flex"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
'project-a/plugin.js': js`
|
|
|
|
|
module.exports = function ({ addVariant }) {
|
|
|
|
|
addVariant('inverted', '@media (inverted-colors: inverted)')
|
|
|
|
|
addVariant('hocus', ['&:focus', '&:hover'])
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'project-a/tailwind.config.js': js`
|
|
|
|
|
module.exports = {
|
|
|
|
|
content: ['../project-b/src/**/*.js'],
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'project-a/src/index.js': js`
|
|
|
|
|
const className = "content-['project-a/src/index.js']"
|
|
|
|
|
module.exports = { className }
|
|
|
|
|
`,
|
|
|
|
|
'project-b/src/index.html': html`
|
|
|
|
|
<div class="flex" />
|
|
|
|
|
`,
|
|
|
|
|
'project-b/src/index.js': js`
|
|
|
|
|
const className = "content-['project-b/src/index.js']"
|
|
|
|
|
module.exports = { className }
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ root, fs, exec }) => {
|
|
|
|
|
await exec(
|
|
|
|
|
`${command} --input - --output dist/out.css`,
|
|
|
|
|
{ cwd: path.join(root, 'project-a') },
|
|
|
|
|
{
|
|
|
|
|
stdin: css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
@config './tailwind.config.js';
|
|
|
|
|
@source '../project-b/src/**/*.html';
|
|
|
|
|
@plugin './plugin.js';
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('project-a/dist/out.css', [
|
|
|
|
|
candidate`underline`,
|
|
|
|
|
candidate`flex`,
|
|
|
|
|
candidate`content-['project-a/src/index.js']`,
|
|
|
|
|
candidate`content-['project-b/src/index.js']`,
|
|
|
|
|
candidate`inverted:flex`,
|
|
|
|
|
candidate`hocus:underline`,
|
|
|
|
|
candidate`*:flex`,
|
|
|
|
|
candidate`**:flex`,
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'production build — (write to stdout)',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`{}`,
|
|
|
|
|
'pnpm-workspace.yaml': yaml`
|
|
|
|
|
#
|
|
|
|
|
packages:
|
|
|
|
|
- project-a
|
|
|
|
|
`,
|
|
|
|
|
'project-a/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'project-a/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="underline 2xl:font-bold hocus:underline inverted:flex *:flex **:flex"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
'project-a/plugin.js': js`
|
|
|
|
|
module.exports = function ({ addVariant }) {
|
|
|
|
|
addVariant('inverted', '@media (inverted-colors: inverted)')
|
|
|
|
|
addVariant('hocus', ['&:focus', '&:hover'])
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'project-a/tailwind.config.js': js`
|
|
|
|
|
module.exports = {
|
|
|
|
|
content: ['../project-b/src/**/*.js'],
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'project-a/src/index.css': css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
@config '../tailwind.config.js';
|
|
|
|
|
@source '../../project-b/src/**/*.html';
|
|
|
|
|
@plugin '../plugin.js';
|
|
|
|
|
`,
|
|
|
|
|
'project-a/src/index.js': js`
|
|
|
|
|
const className = "content-['project-a/src/index.js']"
|
|
|
|
|
module.exports = { className }
|
|
|
|
|
`,
|
|
|
|
|
'project-b/src/index.html': html`
|
|
|
|
|
<div class="flex" />
|
|
|
|
|
`,
|
|
|
|
|
'project-b/src/index.js': js`
|
|
|
|
|
const className = "content-['project-b/src/index.js']"
|
|
|
|
|
module.exports = { className }
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ root, expect, exec }) => {
|
|
|
|
|
let stdout = await exec(`${command} --input src/index.css --output -`, {
|
|
|
|
|
cwd: path.join(root, 'project-a'),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(stdout).toContain(candidate`underline`)
|
|
|
|
|
expect(stdout).toContain(candidate`flex`)
|
|
|
|
|
expect(stdout).toContain(candidate`content-['project-a/src/index.js']`)
|
|
|
|
|
expect(stdout).toContain(candidate`content-['project-b/src/index.js']`)
|
|
|
|
|
expect(stdout).toContain(candidate`inverted:flex`)
|
|
|
|
|
expect(stdout).toContain(candidate`hocus:underline`)
|
|
|
|
|
expect(stdout).toContain(candidate`*:flex`)
|
|
|
|
|
expect(stdout).toContain(candidate`**:flex`)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2024-09-02 15:23:46 +02:00
|
|
|
test(
|
|
|
|
|
'watch mode',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`{}`,
|
|
|
|
|
'pnpm-workspace.yaml': yaml`
|
|
|
|
|
#
|
|
|
|
|
packages:
|
|
|
|
|
- project-a
|
|
|
|
|
`,
|
|
|
|
|
'project-a/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
2024-08-07 16:38:44 +02:00
|
|
|
}
|
2024-09-02 15:23:46 +02:00
|
|
|
`,
|
|
|
|
|
'project-a/index.html': html`
|
|
|
|
|
<div
|
2024-10-01 13:52:12 +02:00
|
|
|
class="underline 2xl:font-bold hocus:underline inverted:flex text-primary"
|
2024-09-02 15:23:46 +02:00
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
'project-a/plugin.js': js`
|
|
|
|
|
module.exports = function ({ addVariant }) {
|
|
|
|
|
addVariant('inverted', '@media (inverted-colors: inverted)')
|
|
|
|
|
addVariant('hocus', ['&:focus', '&:hover'])
|
|
|
|
|
}
|
|
|
|
|
`,
|
2024-09-03 16:54:08 +02:00
|
|
|
'project-a/tailwind.config.js': js`
|
|
|
|
|
module.exports = {
|
|
|
|
|
content: ['../project-b/src/**/*.js'],
|
|
|
|
|
}
|
|
|
|
|
`,
|
2024-09-02 15:23:46 +02:00
|
|
|
'project-a/src/index.css': css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
2024-10-01 13:52:12 +02:00
|
|
|
@import './custom-theme.css';
|
2024-09-03 16:54:08 +02:00
|
|
|
@config '../tailwind.config.js';
|
|
|
|
|
@source '../../project-b/src/**/*.html';
|
2024-09-02 15:23:46 +02:00
|
|
|
@plugin '../plugin.js';
|
|
|
|
|
`,
|
2024-10-01 13:52:12 +02:00
|
|
|
'project-a/src/custom-theme.css': css`
|
|
|
|
|
/* Will be overwritten later */
|
|
|
|
|
@theme {
|
|
|
|
|
--color-primary: black;
|
|
|
|
|
}
|
|
|
|
|
`,
|
2024-09-02 15:23:46 +02:00
|
|
|
'project-a/src/index.js': js`
|
|
|
|
|
const className = "content-['project-a/src/index.js']"
|
|
|
|
|
module.exports = { className }
|
|
|
|
|
`,
|
2024-09-03 16:54:08 +02:00
|
|
|
'project-b/src/index.html': html`
|
|
|
|
|
<div class="flex" />
|
|
|
|
|
`,
|
2024-09-02 15:23:46 +02:00
|
|
|
'project-b/src/index.js': js`
|
|
|
|
|
const className = "content-['project-b/src/index.js']"
|
|
|
|
|
module.exports = { className }
|
|
|
|
|
`,
|
|
|
|
|
},
|
2024-08-07 16:38:44 +02:00
|
|
|
},
|
2024-09-02 15:23:46 +02:00
|
|
|
async ({ root, fs, spawn }) => {
|
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
|
|
|
let process = await spawn(`${command} --input src/index.css --output dist/out.css --watch`, {
|
2024-09-02 15:23:46 +02:00
|
|
|
cwd: path.join(root, 'project-a'),
|
|
|
|
|
})
|
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
|
|
|
await process.onStderr((m) => m.includes('Done in'))
|
2024-08-07 16:38:44 +02:00
|
|
|
|
2024-09-02 15:23:46 +02:00
|
|
|
await fs.expectFileToContain('project-a/dist/out.css', [
|
|
|
|
|
candidate`underline`,
|
2024-09-03 16:54:08 +02:00
|
|
|
candidate`flex`,
|
2024-09-02 15:23:46 +02:00
|
|
|
candidate`content-['project-a/src/index.js']`,
|
|
|
|
|
candidate`content-['project-b/src/index.js']`,
|
|
|
|
|
candidate`inverted:flex`,
|
|
|
|
|
candidate`hocus:underline`,
|
2024-10-01 13:52:12 +02:00
|
|
|
css`
|
|
|
|
|
.text-primary {
|
2024-11-05 15:44:21 -05:00
|
|
|
color: var(--color-primary);
|
2024-10-01 13:52:12 +02:00
|
|
|
}
|
|
|
|
|
`,
|
2024-09-02 15:23:46 +02:00
|
|
|
])
|
2024-08-07 16:38:44 +02:00
|
|
|
|
2024-09-02 15:23:46 +02:00
|
|
|
await fs.write(
|
|
|
|
|
'project-a/src/index.js',
|
|
|
|
|
js`
|
|
|
|
|
const className = "[.changed_&]:content-['project-a/src/index.js']"
|
|
|
|
|
module.exports = { className }
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileToContain('project-a/dist/out.css', [
|
|
|
|
|
candidate`[.changed_&]:content-['project-a/src/index.js']`,
|
|
|
|
|
])
|
2024-08-07 16:38:44 +02:00
|
|
|
|
2024-09-02 15:23:46 +02:00
|
|
|
await fs.write(
|
|
|
|
|
'project-b/src/index.js',
|
|
|
|
|
js`
|
|
|
|
|
const className = "[.changed_&]:content-['project-b/src/index.js']"
|
|
|
|
|
module.exports = { className }
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileToContain('project-a/dist/out.css', [
|
|
|
|
|
candidate`[.changed_&]:content-['project-b/src/index.js']`,
|
|
|
|
|
])
|
2024-10-01 13:52:12 +02:00
|
|
|
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-a/src/custom-theme.css',
|
|
|
|
|
css`
|
|
|
|
|
/* Overriding the primary color */
|
|
|
|
|
@theme {
|
|
|
|
|
--color-primary: red;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('project-a/dist/out.css', [
|
|
|
|
|
css`
|
|
|
|
|
.text-primary {
|
2024-11-05 15:44:21 -05:00
|
|
|
color: var(--color-primary);
|
2024-10-01 13:52:12 +02:00
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
])
|
2024-10-21 16:29:33 -04:00
|
|
|
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-a/src/index.css',
|
|
|
|
|
css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
@theme {
|
|
|
|
|
--color-*: initial;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
|
2025-01-31 17:56:52 +01:00
|
|
|
await fs.expectFileToContain('project-a/dist/out.css', [css``])
|
2024-09-02 15:23:46 +02:00
|
|
|
},
|
|
|
|
|
)
|
2024-09-26 12:32:46 +02:00
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'production build (stdin)',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.html': html`
|
|
|
|
|
<div class="underline"></div>
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`@import 'tailwindcss';`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, exec }) => {
|
|
|
|
|
await exec(`${command} --input=- --output dist/out.css < src/index.css`)
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('dist/out.css', [candidate`underline`])
|
|
|
|
|
},
|
|
|
|
|
)
|
2024-11-19 18:39:49 +01:00
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'module resolution using CJS, ESM, CTS, and MTS',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`{}`,
|
|
|
|
|
'pnpm-workspace.yaml': yaml`
|
|
|
|
|
#
|
|
|
|
|
packages:
|
|
|
|
|
- project-cjs
|
|
|
|
|
- project-esm
|
|
|
|
|
- plugin-cjs
|
|
|
|
|
- plugin-esm
|
|
|
|
|
- plugin-cts
|
|
|
|
|
- plugin-mts
|
|
|
|
|
`,
|
|
|
|
|
'project-cjs/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"type": "commonjs",
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^",
|
|
|
|
|
"plugin-cjs": "workspace:*",
|
|
|
|
|
"plugin-esm": "workspace:*",
|
|
|
|
|
"plugin-cts": "workspace:*",
|
|
|
|
|
"plugin-mts": "workspace:*"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'project-cjs/index.html': html`
|
|
|
|
|
<div class="cjs esm cts mts"></div>
|
|
|
|
|
`,
|
|
|
|
|
'project-cjs/src/index.css': css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
@plugin 'plugin-cjs';
|
|
|
|
|
@plugin 'plugin-esm';
|
|
|
|
|
@plugin 'plugin-cts';
|
|
|
|
|
@plugin 'plugin-mts';
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
'project-esm/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"type": "module",
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^",
|
|
|
|
|
"plugin-cjs": "workspace:*",
|
|
|
|
|
"plugin-esm": "workspace:*",
|
|
|
|
|
"plugin-cts": "workspace:*",
|
|
|
|
|
"plugin-mts": "workspace:*"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'project-esm/index.html': html`
|
|
|
|
|
<div class="cjs esm cts mts"></div>
|
|
|
|
|
`,
|
|
|
|
|
'project-esm/src/index.css': css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
@plugin 'plugin-cjs';
|
|
|
|
|
@plugin 'plugin-esm';
|
|
|
|
|
@plugin 'plugin-cts';
|
|
|
|
|
@plugin 'plugin-mts';
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
'plugin-cjs/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"name": "plugin-cjs",
|
|
|
|
|
"type": "commonjs",
|
|
|
|
|
"exports": {
|
|
|
|
|
".": {
|
|
|
|
|
"require": "./index.cjs"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'plugin-cjs/index.cjs': js`
|
|
|
|
|
module.exports = function ({ addUtilities }) {
|
|
|
|
|
addUtilities({ '.cjs': { content: '"cjs"' } })
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
'plugin-esm/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"name": "plugin-esm",
|
|
|
|
|
"type": "module",
|
|
|
|
|
"exports": {
|
|
|
|
|
".": {
|
|
|
|
|
"import": "./index.mjs"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'plugin-esm/index.mjs': js`
|
|
|
|
|
export default function ({ addUtilities }) {
|
|
|
|
|
addUtilities({ '.esm': { content: '"esm"' } })
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
'plugin-cts/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"name": "plugin-cts",
|
|
|
|
|
"type": "commonjs",
|
|
|
|
|
"exports": {
|
|
|
|
|
".": {
|
|
|
|
|
"require": "./index.cts"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'plugin-cts/index.cts': ts`
|
|
|
|
|
export default function ({ addUtilities }) {
|
|
|
|
|
addUtilities({ '.cts': { content: '"cts"' as const } })
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
'plugin-mts/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"name": "plugin-mts",
|
|
|
|
|
"type": "module",
|
|
|
|
|
"exports": {
|
|
|
|
|
".": {
|
|
|
|
|
"import": "./index.mts"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'plugin-mts/index.mts': ts`
|
|
|
|
|
export default function ({ addUtilities }) {
|
|
|
|
|
addUtilities({ '.mts': { content: '"mts"' as const } })
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ root, fs, exec }) => {
|
|
|
|
|
await exec(`${command} --input src/index.css --output dist/out.css`, {
|
|
|
|
|
cwd: path.join(root, 'project-cjs'),
|
|
|
|
|
})
|
|
|
|
|
await exec(`${command} --input src/index.css --output dist/out.css`, {
|
|
|
|
|
cwd: path.join(root, 'project-esm'),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('./project-cjs/dist/out.css', [
|
|
|
|
|
candidate`cjs`,
|
|
|
|
|
candidate`esm`,
|
|
|
|
|
candidate`cts`,
|
|
|
|
|
candidate`mts`,
|
|
|
|
|
])
|
|
|
|
|
await fs.expectFileToContain('./project-esm/dist/out.css', [
|
|
|
|
|
candidate`cjs`,
|
|
|
|
|
candidate`esm`,
|
|
|
|
|
candidate`cts`,
|
|
|
|
|
candidate`mts`,
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
)
|
2025-01-28 11:13:16 -05:00
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'git ignore files outside of a repo are not considered',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
// Ignore everything in the "home" directory
|
|
|
|
|
'home/.gitignore': '*',
|
|
|
|
|
|
|
|
|
|
// Only ignore files called ignore-*.html in the actual git repo
|
|
|
|
|
'home/project/.gitignore': 'ignore-*.html',
|
|
|
|
|
|
|
|
|
|
'home/project/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"type": "module",
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
'home/project/src/index.css': css` @import 'tailwindcss'; `,
|
|
|
|
|
'home/project/src/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['index.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
'home/project/src/ignore-1.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['ignore-1.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
'home/project/src/ignore-2.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['ignore-2.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
installDependencies: false,
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, root, exec }) => {
|
|
|
|
|
await exec(`pnpm install --ignore-workspace`, {
|
|
|
|
|
cwd: path.join(root, 'home/project'),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// No git repo = all ignore files are considered
|
|
|
|
|
await exec(`${command} --input src/index.css --output dist/out.css`, {
|
|
|
|
|
cwd: path.join(root, 'home/project'),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await fs.expectFileNotToContain('./home/project/dist/out.css', [
|
|
|
|
|
candidate`content-['index.html']`,
|
|
|
|
|
candidate`content-['ignore-1.html']`,
|
|
|
|
|
candidate`content-['ignore-2.html']`,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// Make home/project a git repo
|
|
|
|
|
// Only ignore files within the repo are considered
|
|
|
|
|
await exec(`git init`, {
|
|
|
|
|
cwd: path.join(root, 'home/project'),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await exec(`${command} --input src/index.css --output dist/out.css`, {
|
|
|
|
|
cwd: path.join(root, 'home/project'),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('./home/project/dist/out.css', [
|
|
|
|
|
candidate`content-['index.html']`,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
await fs.expectFileNotToContain('./home/project/dist/out.css', [
|
|
|
|
|
candidate`content-['ignore-1.html']`,
|
|
|
|
|
candidate`content-['ignore-2.html']`,
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
)
|
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
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'production build + inline source maps',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'ssrc/index.html': html`
|
|
|
|
|
<div class="flex"></div>
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
/* */
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ exec, expect, fs, parseSourceMap }) => {
|
|
|
|
|
await exec(`${command} --input src/index.css --output dist/out.css --map`)
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('dist/out.css', [candidate`flex`])
|
|
|
|
|
|
|
|
|
|
// Make sure we can find a source map
|
|
|
|
|
let map = parseSourceMap(await fs.read('dist/out.css'))
|
|
|
|
|
|
|
|
|
|
expect(map.at(1, 0)).toMatchObject({
|
|
|
|
|
source: null,
|
|
|
|
|
original: '(none)',
|
|
|
|
|
generated: '/*! tailwi...',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(map.at(2, 0)).toMatchObject({
|
|
|
|
|
source:
|
|
|
|
|
kind === 'CLI'
|
2025-05-08 16:56:49 -04:00
|
|
|
? expect.stringContaining('utilities.css')
|
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
|
|
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
|
|
|
|
original: '@tailwind...',
|
|
|
|
|
generated: '.flex {...',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(map.at(3, 2)).toMatchObject({
|
|
|
|
|
source:
|
|
|
|
|
kind === 'CLI'
|
2025-05-08 16:56:49 -04:00
|
|
|
? expect.stringContaining('utilities.css')
|
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
|
|
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
|
|
|
|
original: '@tailwind...',
|
|
|
|
|
generated: 'display: f...',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(map.at(4, 0)).toMatchObject({
|
|
|
|
|
source: null,
|
|
|
|
|
original: '(none)',
|
|
|
|
|
generated: '}...',
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'production build + separate source maps',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'ssrc/index.html': html`
|
|
|
|
|
<div class="flex"></div>
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
/* */
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ exec, expect, fs, parseSourceMap }) => {
|
|
|
|
|
await exec(`${command} --input src/index.css --output dist/out.css --map dist/out.css.map`)
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('dist/out.css', [candidate`flex`])
|
|
|
|
|
|
|
|
|
|
// Make sure we can find a source map
|
|
|
|
|
let map = parseSourceMap({
|
|
|
|
|
map: await fs.read('dist/out.css.map'),
|
|
|
|
|
content: await fs.read('dist/out.css'),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(map.at(1, 0)).toMatchObject({
|
|
|
|
|
source: null,
|
|
|
|
|
original: '(none)',
|
|
|
|
|
generated: '/*! tailwi...',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(map.at(2, 0)).toMatchObject({
|
|
|
|
|
source:
|
|
|
|
|
kind === 'CLI'
|
2025-05-08 16:56:49 -04:00
|
|
|
? expect.stringContaining('utilities.css')
|
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
|
|
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
|
|
|
|
original: '@tailwind...',
|
|
|
|
|
generated: '.flex {...',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(map.at(3, 2)).toMatchObject({
|
|
|
|
|
source:
|
|
|
|
|
kind === 'CLI'
|
2025-05-08 16:56:49 -04:00
|
|
|
? expect.stringContaining('utilities.css')
|
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
|
|
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
|
|
|
|
original: '@tailwind...',
|
|
|
|
|
generated: 'display: f...',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(map.at(4, 0)).toMatchObject({
|
|
|
|
|
source: null,
|
|
|
|
|
original: '(none)',
|
|
|
|
|
generated: '}...',
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Skipped because Lightning CSS has a bug with source maps containing
|
|
|
|
|
// license comments and it breaks stuff.
|
|
|
|
|
test.skip(
|
|
|
|
|
'production build + minify + source maps',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'ssrc/index.html': html`
|
|
|
|
|
<div class="flex"></div>
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
/* */
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ exec, expect, fs, parseSourceMap }) => {
|
|
|
|
|
await exec(`${command} --input src/index.css --output dist/out.css --minify --map`)
|
|
|
|
|
|
|
|
|
|
await fs.expectFileToContain('dist/out.css', [candidate`flex`])
|
|
|
|
|
|
|
|
|
|
// Make sure we can find a source map
|
|
|
|
|
let map = parseSourceMap(await fs.read('dist/out.css'))
|
|
|
|
|
|
|
|
|
|
expect(map.at(1, 0)).toMatchObject({
|
|
|
|
|
source: null,
|
|
|
|
|
original: '(none)',
|
|
|
|
|
generated: '/*! tailwi...',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(map.at(2, 0)).toMatchObject({
|
|
|
|
|
source:
|
|
|
|
|
kind === 'CLI'
|
2025-05-08 16:56:49 -04:00
|
|
|
? expect.stringContaining('utilities.css')
|
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
|
|
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
|
|
|
|
original: '@tailwind...',
|
|
|
|
|
generated: '.flex {...',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(map.at(3, 2)).toMatchObject({
|
|
|
|
|
source:
|
|
|
|
|
kind === 'CLI'
|
2025-05-08 16:56:49 -04:00
|
|
|
? expect.stringContaining('utilities.css')
|
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
|
|
|
: expect.stringMatching(/\/utilities-\w+\.css$/),
|
|
|
|
|
original: '@tailwind...',
|
|
|
|
|
generated: 'display: f...',
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(map.at(4, 0)).toMatchObject({
|
|
|
|
|
source: null,
|
|
|
|
|
original: '(none)',
|
|
|
|
|
generated: '}...',
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
)
|
2024-09-02 15:23:46 +02:00
|
|
|
})
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'auto source detection kitchen sink',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.css': css`
|
2025-02-25 11:36:43 +01:00
|
|
|
@reference 'tailwindcss/theme';
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
|
|
|
|
|
/* (1) */
|
|
|
|
|
/* - Only './src' should be auto-scanned, not the current working directory */
|
|
|
|
|
/* - .gitignore'd paths should be ignored (node_modules) */
|
|
|
|
|
/* - Binary extensions should be ignored (jpg, zip) */
|
|
|
|
|
@import 'tailwindcss/utilities' source('./src');
|
|
|
|
|
|
|
|
|
|
/* (2) */
|
|
|
|
|
/* - All HTML and JSX files in 'ignored/components' should be scanned */
|
|
|
|
|
/* - All other extensions should be ignored */
|
|
|
|
|
@source "./ignored/components/*.{html,jsx}";
|
|
|
|
|
|
|
|
|
|
/* (3) */
|
|
|
|
|
/* - './components' should be auto-scanned in addition to './src' */
|
|
|
|
|
/* - './components/ignored.html' should still be ignored */
|
|
|
|
|
/* - Binary extensions in './components' should be ignored */
|
|
|
|
|
@source "./components";
|
|
|
|
|
|
|
|
|
|
/* (4) */
|
|
|
|
|
/* - './pages' should be auto-scanned */
|
|
|
|
|
/* - Only '.html' files should be included */
|
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
|
|
|
/* - './page/ignored.html' will not be ignored because of the specific pattern */
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
@source "./pages/**/*.html";
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
'.gitignore': dedent`
|
|
|
|
|
/src/ignored
|
|
|
|
|
/ignored
|
|
|
|
|
/components/ignored.html
|
|
|
|
|
/pages/ignored.html
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// (1)
|
|
|
|
|
'index.html': 'content-["index.html"] content-["BAD"]', // "Root" source is in `./src`
|
|
|
|
|
'src/index.html': 'content-["src/index.html"]',
|
|
|
|
|
'src/nested/index.html': 'content-["src/nested/index.html"]',
|
|
|
|
|
'src/index.jpg': 'content-["src/index.jpg"] content-["BAD"]',
|
|
|
|
|
'src/nested/index.tar': 'content-["src/nested/index.tar"] content-["BAD"]',
|
|
|
|
|
'src/ignored/index.html': 'content-["src/ignored/index.html"] content-["BAD"]',
|
|
|
|
|
|
|
|
|
|
// (2)
|
|
|
|
|
'ignored/components/my-component.html': 'content-["ignored/components/my-component.html"]',
|
|
|
|
|
'ignored/components/my-component.jsx': 'content-["ignored/components/my-component.jsx"]',
|
|
|
|
|
|
|
|
|
|
// Ignored and not explicitly listed by (2)
|
|
|
|
|
'ignored/components/my-component.tsx':
|
|
|
|
|
'content-["ignored/components/my-component.tsx"] content-["BAD"]',
|
|
|
|
|
'ignored/components/nested/my-component.html':
|
|
|
|
|
'content-["ignored/components/nested/my-component.html"] content-["BAD"]',
|
|
|
|
|
|
|
|
|
|
// (3)
|
|
|
|
|
'components/my-component.tsx': 'content-["components/my-component.tsx"]',
|
|
|
|
|
'components/nested/my-component.tsx': 'content-["components/nested/my-component.tsx"]',
|
|
|
|
|
'components/ignored.html': 'content-["components/ignored.html"] content-["BAD"]',
|
|
|
|
|
|
|
|
|
|
// (4)
|
|
|
|
|
'pages/foo.html': 'content-["pages/foo.html"]',
|
|
|
|
|
'pages/nested/foo.html': 'content-["pages/nested/foo.html"]',
|
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
|
|
|
'pages/ignored.html': 'content-["pages/ignored.html"]',
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
'pages/foo.jsx': 'content-["pages/foo.jsx"] content-["BAD"]',
|
|
|
|
|
'pages/nested/foo.jsx': 'content-["pages/nested/foo.jsx"] content-["BAD"]',
|
|
|
|
|
},
|
|
|
|
|
},
|
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
|
|
|
async ({ fs, exec, expect }) => {
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
await exec('pnpm tailwindcss --input index.css --output dist/out.css')
|
|
|
|
|
|
|
|
|
|
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
--- ./dist/out.css ---
|
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:

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
|
|
|
@layer properties;
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
.content-\\[\\"components\\/my-component\\.tsx\\"\\] {
|
|
|
|
|
--tw-content: "components/my-component.tsx";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\"components\\/nested\\/my-component\\.tsx\\"\\] {
|
|
|
|
|
--tw-content: "components/nested/my-component.tsx";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\"ignored\\/components\\/my-component\\.html\\"\\] {
|
|
|
|
|
--tw-content: "ignored/components/my-component.html";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\"ignored\\/components\\/my-component\\.jsx\\"\\] {
|
|
|
|
|
--tw-content: "ignored/components/my-component.jsx";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\"pages\\/foo\\.html\\"\\] {
|
|
|
|
|
--tw-content: "pages/foo.html";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
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
|
|
|
.content-\\[\\"pages\\/ignored\\.html\\"\\] {
|
|
|
|
|
--tw-content: "pages/ignored.html";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
.content-\\[\\"pages\\/nested\\/foo\\.html\\"\\] {
|
|
|
|
|
--tw-content: "pages/nested/foo.html";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\"src\\/index\\.html\\"\\] {
|
|
|
|
|
--tw-content: "src/index.html";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\"src\\/nested\\/index\\.html\\"\\] {
|
|
|
|
|
--tw-content: "src/nested/index.html";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
@property --tw-content {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: "";
|
|
|
|
|
}
|
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:

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
|
|
|
@layer properties {
|
|
|
|
|
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
|
|
|
|
*, ::before, ::after, ::backdrop {
|
|
|
|
|
--tw-content: "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'auto source detection in depth, source(…) and `@source` can be configured to use auto source detection (build + watch mode)',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`{}`,
|
|
|
|
|
'pnpm-workspace.yaml': yaml`
|
|
|
|
|
#
|
|
|
|
|
packages:
|
|
|
|
|
- project-a
|
|
|
|
|
`,
|
|
|
|
|
'project-a/package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'project-a/src/index.css': css`
|
2025-02-25 11:36:43 +01:00
|
|
|
@reference 'tailwindcss/theme';
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
|
|
|
|
|
/* Run auto-content detection in ../../project-b */
|
|
|
|
|
@import 'tailwindcss/utilities' source('../../project-b');
|
|
|
|
|
|
|
|
|
|
/* Explicitly using node_modules in the @source allows git ignored folders */
|
|
|
|
|
@source '../node_modules/{my-lib-1,my-lib-2}/src/**/*.html';
|
|
|
|
|
|
|
|
|
|
/* We typically ignore these extensions, but now include them explicitly */
|
|
|
|
|
@source './logo.{jpg,png}';
|
|
|
|
|
|
|
|
|
|
/* Project C should apply auto source detection */
|
|
|
|
|
@source '../../project-c';
|
|
|
|
|
|
|
|
|
|
/* Project D should apply auto source detection rules, such as ignoring node_modules */
|
|
|
|
|
@source '../../project-d/**/*.{html,js}';
|
|
|
|
|
@source '../../project-d/**/*.bin';
|
|
|
|
|
|
|
|
|
|
/* Same as above, but my-lib-2 _should_ be includes */
|
|
|
|
|
@source '../../project-d/node_modules/my-lib-2/src/*.{html,js}';
|
|
|
|
|
|
|
|
|
|
/* bar.html is git ignored, but explicitly listed here to scan */
|
|
|
|
|
@source '../../project-d/src/bar.html';
|
2024-10-30 21:24:48 +01:00
|
|
|
|
|
|
|
|
/* Project E's source ends with '..' */
|
|
|
|
|
@source '../../project-e/nested/..';
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project A is the current folder, but we explicitly configured
|
|
|
|
|
// `source(project-b)`, therefore project-a should not be included in
|
|
|
|
|
// the output.
|
|
|
|
|
'project-a/src/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['SHOULD-NOT-EXIST-IN-OUTPUT'] content-['project-a/src/index.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project A explicitly includes an extension we usually ignore,
|
|
|
|
|
// therefore it should be included in the output.
|
|
|
|
|
'project-a/src/logo.jpg': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['project-a/src/logo.jpg']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project A explicitly includes node_modules/{my-lib-1,my-lib-2},
|
|
|
|
|
// therefore these files should be included in the output.
|
|
|
|
|
'project-a/node_modules/my-lib-1/src/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['project-a/node_modules/my-lib-1/src/index.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
'project-a/node_modules/my-lib-2/src/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['project-a/node_modules/my-lib-2/src/index.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project B is the configured `source(…)`, therefore auto source
|
|
|
|
|
// detection should include known extensions and folders in the output.
|
|
|
|
|
'project-b/src/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['project-b/src/index.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project B is the configured `source(…)`, therefore auto source
|
|
|
|
|
// detection should apply and node_modules should not be included in the
|
|
|
|
|
// output.
|
|
|
|
|
'project-b/node_modules/my-lib-3/src/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['SHOULD-NOT-EXIST-IN-OUTPUT'] content-['project-b/node_modules/my-lib-3/src/index.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project C should apply auto source detection, therefore known
|
|
|
|
|
// extensions and folders should be included in the output.
|
|
|
|
|
'project-c/src/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['project-c/src/index.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project C should apply auto source detection, therefore known ignored
|
|
|
|
|
// extensions should not be included in the output.
|
|
|
|
|
'project-c/src/logo.jpg': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['SHOULD-NOT-EXIST-IN-OUTPUT'] content-['project-c/src/logo.jpg']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project C should apply auto source detection, therefore node_modules
|
|
|
|
|
// should not be included in the output.
|
|
|
|
|
'project-c/node_modules/my-lib-1/src/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['SHOULD-NOT-EXIST-IN-OUTPUT'] content-['project-c/node_modules/my-lib-1/src/index.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project D should apply auto source detection rules, such as ignoring
|
|
|
|
|
// node_modules.
|
|
|
|
|
'project-d/node_modules/my-lib-1/src/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['SHOULD-NOT-EXIST-IN-OUTPUT'] content-['project-d/node_modules/my-lib-1/src/index.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project D has an explicit glob containing node_modules, thus should include the html file
|
|
|
|
|
'project-d/node_modules/my-lib-2/src/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['project-d/node_modules/my-lib-2/src/index.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
'project-d/src/.gitignore': dedent`
|
|
|
|
|
foo.html
|
|
|
|
|
bar.html
|
|
|
|
|
`,
|
|
|
|
|
|
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
|
|
|
// Project D, foo.html is ignored by the gitignore file but the source rule is explicit about
|
|
|
|
|
// adding all `.html` files.
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
'project-d/src/foo.html': html`
|
|
|
|
|
<div
|
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
|
|
|
class="content-['project-d/src/foo.html']"
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project D, bar.html is ignored by the gitignore file. But explicitly
|
|
|
|
|
// listed as a `@source` glob.
|
|
|
|
|
'project-d/src/bar.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['project-d/src/bar.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project D should look for files with the extensions html and js.
|
|
|
|
|
'project-d/src/index.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['project-d/src/index.html']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project D should have a binary file even though we ignore binary files
|
|
|
|
|
// by default, but it's explicitly listed.
|
|
|
|
|
'project-d/my-binary-file.bin': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['project-d/my-binary-file.bin']"
|
|
|
|
|
></div>
|
|
|
|
|
`,
|
2024-10-30 21:24:48 +01:00
|
|
|
|
|
|
|
|
// Project E's `@source "project-e/nested/.."` ends with `..`, which
|
|
|
|
|
// should look for files in `project-e` itself.
|
|
|
|
|
'project-e/index.html': html`<div class="content-['project-e/index.html']"></div>`,
|
|
|
|
|
'project-e/nested/index.html': html`<div
|
|
|
|
|
class="content-['project-e/nested/index.html']"
|
|
|
|
|
></div>`,
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
},
|
|
|
|
|
},
|
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
|
|
|
async ({ fs, exec, spawn, root, expect }) => {
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
await exec('pnpm tailwindcss --input src/index.css --output dist/out.css', {
|
|
|
|
|
cwd: path.join(root, 'project-a'),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(await fs.dumpFiles('./project-a/dist/*.css')).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
--- ./project-a/dist/out.css ---
|
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:

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
|
|
|
@layer properties;
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
.content-\\[\\'project-a\\/node_modules\\/my-lib-1\\/src\\/index\\.html\\'\\] {
|
|
|
|
|
--tw-content: 'project-a/node modules/my-lib-1/src/index.html';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\'project-a\\/node_modules\\/my-lib-2\\/src\\/index\\.html\\'\\] {
|
|
|
|
|
--tw-content: 'project-a/node modules/my-lib-2/src/index.html';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\'project-a\\/src\\/logo\\.jpg\\'\\] {
|
|
|
|
|
--tw-content: 'project-a/src/logo.jpg';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\'project-b\\/src\\/index\\.html\\'\\] {
|
|
|
|
|
--tw-content: 'project-b/src/index.html';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\'project-c\\/src\\/index\\.html\\'\\] {
|
|
|
|
|
--tw-content: 'project-c/src/index.html';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\'project-d\\/my-binary-file\\.bin\\'\\] {
|
|
|
|
|
--tw-content: 'project-d/my-binary-file.bin';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\'project-d\\/node_modules\\/my-lib-2\\/src\\/index\\.html\\'\\] {
|
|
|
|
|
--tw-content: 'project-d/node modules/my-lib-2/src/index.html';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\'project-d\\/src\\/bar\\.html\\'\\] {
|
|
|
|
|
--tw-content: 'project-d/src/bar.html';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
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
|
|
|
.content-\\[\\'project-d\\/src\\/foo\\.html\\'\\] {
|
|
|
|
|
--tw-content: 'project-d/src/foo.html';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
.content-\\[\\'project-d\\/src\\/index\\.html\\'\\] {
|
|
|
|
|
--tw-content: 'project-d/src/index.html';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
2024-10-30 21:24:48 +01:00
|
|
|
.content-\\[\\'project-e\\/index\\.html\\'\\] {
|
|
|
|
|
--tw-content: 'project-e/index.html';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\'project-e\\/nested\\/index\\.html\\'\\] {
|
|
|
|
|
--tw-content: 'project-e/nested/index.html';
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
@property --tw-content {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: "";
|
|
|
|
|
}
|
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:

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
|
|
|
@layer properties {
|
|
|
|
|
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
|
|
|
|
*, ::before, ::after, ::backdrop {
|
|
|
|
|
--tw-content: "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
// Watch mode tests
|
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
|
|
|
let process = await spawn(
|
|
|
|
|
'pnpm tailwindcss --input src/index.css --output dist/out.css --watch',
|
|
|
|
|
{
|
|
|
|
|
cwd: path.join(root, 'project-a'),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
await process.onStderr((m) => m.includes('Done in'))
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
|
|
|
|
|
// Changes to project-a should not be included in the output, we changed the
|
|
|
|
|
// base folder to project-b.
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-a/src/index.html',
|
|
|
|
|
html`<div class="[.changed_&]:content-['project-a/src/index.html']"></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileNotToContain('./project-a/dist/out.css', [
|
|
|
|
|
candidate`[.changed_&]:content-['project-a/src/index.html']`,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// Changes to this file should be included, because we explicitly listed
|
|
|
|
|
// them using `@source`.
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-a/src/logo.jpg',
|
|
|
|
|
html`<div class="[.changed_&]:content-['project-a/src/logo.jpg']"></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileToContain('./project-a/dist/out.css', [
|
|
|
|
|
candidate`[.changed_&]:content-['project-a/src/logo.jpg']`,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// Changes to these files should be included, because we explicitly listed
|
|
|
|
|
// them using `@source`.
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-a/node_modules/my-lib-1/src/index.html',
|
|
|
|
|
html`<div
|
|
|
|
|
class="[.changed_&]:content-['project-a/node_modules/my-lib-1/src/index.html']"
|
|
|
|
|
></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileToContain('./project-a/dist/out.css', [
|
|
|
|
|
candidate`[.changed_&]:content-['project-a/node_modules/my-lib-1/src/index.html']`,
|
|
|
|
|
])
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-a/node_modules/my-lib-2/src/index.html',
|
|
|
|
|
html`<div
|
|
|
|
|
class="[.changed_&]:content-['project-a/node_modules/my-lib-2/src/index.html']"
|
|
|
|
|
></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileToContain('./project-a/dist/out.css', [
|
|
|
|
|
candidate`[.changed_&]:content-['project-a/node_modules/my-lib-2/src/index.html']`,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// Changes to this file should be included, because we changed the base to
|
|
|
|
|
// `project-b`.
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-b/src/index.html',
|
|
|
|
|
html`<div class="[.changed_&]:content-['project-b/src/index.html']"></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileToContain('./project-a/dist/out.css', [
|
|
|
|
|
candidate`[.changed_&]:content-['project-b/src/index.html']`,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// Changes to this file should not be included. We did change the base to
|
|
|
|
|
// `project-b`, but we still apply the auto source detection rules which
|
|
|
|
|
// ignore `node_modules`.
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-b/node_modules/my-lib-3/src/index.html',
|
|
|
|
|
html`<div
|
|
|
|
|
class="[.changed_&]:content-['project-b/node_modules/my-lib-3/src/index.html']"
|
|
|
|
|
></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileNotToContain('./project-a/dist/out.css', [
|
|
|
|
|
candidate`[.changed_&]:content-['project-b/node_modules/my-lib-3/src/index.html']`,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// Project C was added explicitly via `@source`, therefore changes to these
|
|
|
|
|
// files should be included.
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-c/src/index.html',
|
|
|
|
|
html`<div class="[.changed_&]:content-['project-c/src/index.html']"></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileToContain('./project-a/dist/out.css', [
|
|
|
|
|
candidate`[.changed_&]:content-['project-c/src/index.html']`,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// Except for these files, since they are ignored by the default auto source
|
|
|
|
|
// detection rules.
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-c/src/logo.jpg',
|
|
|
|
|
html`<div class="[.changed_&]:content-['project-c/src/logo.jpg']"></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileNotToContain('./project-a/dist/out.css', [
|
|
|
|
|
candidate`[.changed_&]:content-['project-c/src/logo.jpg']`,
|
|
|
|
|
])
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-c/node_modules/my-lib-1/src/index.html',
|
|
|
|
|
html`<div
|
|
|
|
|
class="[.changed_&]:content-['project-c/node_modules/my-lib-1/src/index.html']"
|
|
|
|
|
></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileNotToContain('./project-a/dist/out.css', [
|
|
|
|
|
candidate`[.changed_&]:content-['project-c/node_modules/my-lib-1/src/index.html']`,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// Creating new files in the "root" of auto source detected folders
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-b/new-file.html',
|
|
|
|
|
html`<div class="[.created_&]:content-['project-b/new-file.html']"></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-b/new-folder/new-file.html',
|
|
|
|
|
html`<div class="[.created_&]:content-['project-b/new-folder/new-file.html']"></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-c/new-file.html',
|
|
|
|
|
html`<div class="[.created_&]:content-['project-c/new-file.html']"></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.write(
|
|
|
|
|
'project-c/new-folder/new-file.html',
|
|
|
|
|
html`<div class="[.created_&]:content-['project-c/new-folder/new-file.html']"></div>`,
|
|
|
|
|
)
|
|
|
|
|
await fs.expectFileToContain('./project-a/dist/out.css', [
|
|
|
|
|
candidate`[.created_&]:content-['project-b/new-file.html']`,
|
|
|
|
|
candidate`[.created_&]:content-['project-b/new-folder/new-file.html']`,
|
|
|
|
|
candidate`[.created_&]:content-['project-c/new-file.html']`,
|
|
|
|
|
candidate`[.created_&]:content-['project-c/new-folder/new-file.html']`,
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'auto source detection disabled',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.css': css`
|
2025-02-25 11:36:43 +01:00
|
|
|
@reference 'tailwindcss/theme';
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
|
|
|
|
|
/* (1) */
|
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
|
|
|
/* - Disable auto-source detection */
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
@import 'tailwindcss/utilities' source(none);
|
|
|
|
|
|
|
|
|
|
/* (2) */
|
|
|
|
|
/* - './pages' should be auto-scanned */
|
|
|
|
|
/* - Only '.html' files should be included */
|
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
|
|
|
/* - './page/ignored.html' will not be ignored because of the specific pattern */
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
@source "./pages/**/*.html";
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
'.gitignore': dedent`
|
|
|
|
|
/src/ignored
|
|
|
|
|
/pages/ignored.html
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// (1)
|
|
|
|
|
'index.html': 'content-["index.html"] content-["BAD"]', // "Root" source is in `./src`
|
|
|
|
|
'src/index.html': 'content-["src/index.html"] content-["BAD"]',
|
|
|
|
|
'src/nested/index.html': 'content-["src/nested/index.html"] content-["BAD"]',
|
|
|
|
|
'src/index.jpg': 'content-["src/index.jpg"] content-["BAD"]',
|
|
|
|
|
'src/nested/index.tar': 'content-["src/nested/index.tar"] content-["BAD"]',
|
|
|
|
|
'src/ignored/index.html': 'content-["src/ignored/index.html"] content-["BAD"]',
|
|
|
|
|
|
|
|
|
|
// (4)
|
|
|
|
|
'pages/foo.html': 'content-["pages/foo.html"]',
|
|
|
|
|
'pages/nested/foo.html': 'content-["pages/nested/foo.html"]',
|
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
|
|
|
'pages/ignored.html': 'content-["pages/ignored.html"]',
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
'pages/foo.jsx': 'content-["pages/foo.jsx"] content-["BAD"]',
|
|
|
|
|
'pages/nested/foo.jsx': 'content-["pages/nested/foo.jsx"] content-["BAD"]',
|
|
|
|
|
},
|
|
|
|
|
},
|
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
|
|
|
async ({ fs, exec, expect }) => {
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
await exec('pnpm tailwindcss --input index.css --output dist/out.css')
|
|
|
|
|
|
|
|
|
|
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
--- ./dist/out.css ---
|
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:

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
|
|
|
@layer properties;
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
.content-\\[\\"pages\\/foo\\.html\\"\\] {
|
|
|
|
|
--tw-content: "pages/foo.html";
|
|
|
|
|
content: var(--tw-content);
|
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
|
|
|
}
|
|
|
|
|
.content-\\[\\"pages\\/ignored\\.html\\"\\] {
|
|
|
|
|
--tw-content: "pages/ignored.html";
|
|
|
|
|
content: var(--tw-content);
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
}
|
|
|
|
|
.content-\\[\\"pages\\/nested\\/foo\\.html\\"\\] {
|
|
|
|
|
--tw-content: "pages/nested/foo.html";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
@property --tw-content {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: "";
|
|
|
|
|
}
|
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:

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
|
|
|
@layer properties {
|
|
|
|
|
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
|
|
|
|
*, ::before, ::after, ::backdrop {
|
|
|
|
|
--tw-content: "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
Auto source detection improvements (#14820)
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>
2024-10-29 21:33:34 +01:00
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
},
|
|
|
|
|
)
|
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
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'@theme reference should never emit values',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@reference "tailwindcss";
|
|
|
|
|
|
|
|
|
|
.keep-me {
|
|
|
|
|
color: red;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, spawn, expect }) => {
|
|
|
|
|
let process = await spawn(
|
|
|
|
|
`pnpm tailwindcss --input src/index.css --output dist/out.css --watch`,
|
|
|
|
|
)
|
|
|
|
|
await process.onStderr((m) => m.includes('Done in'))
|
|
|
|
|
|
|
|
|
|
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
--- ./dist/out.css ---
|
|
|
|
|
.keep-me {
|
|
|
|
|
color: red;
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
await fs.write(
|
|
|
|
|
'./src/index.css',
|
|
|
|
|
css`
|
|
|
|
|
@reference "tailwindcss";
|
|
|
|
|
|
|
|
|
|
/* Not a reference! */
|
|
|
|
|
@theme {
|
|
|
|
|
--color-pink: pink;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.keep-me {
|
|
|
|
|
color: red;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
--- ./dist/out.css ---
|
|
|
|
|
.keep-me {
|
|
|
|
|
color: red;
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
},
|
|
|
|
|
)
|
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 18:12:47 +01:00
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'emit CSS variables if used outside of utilities',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'src/index.css': css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
@theme {
|
|
|
|
|
--color-blue-500: blue;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'src/index.ts': ts`
|
|
|
|
|
function MyComponent() {
|
|
|
|
|
return <motion.div />
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, spawn, expect }) => {
|
|
|
|
|
let process = await spawn(
|
|
|
|
|
'pnpm tailwindcss --input src/index.css --output dist/out.css --watch',
|
|
|
|
|
)
|
|
|
|
|
await process.onStderr((m) => m.includes('Done in'))
|
|
|
|
|
|
|
|
|
|
// No CSS variables are used, so nothing should be generated yet.
|
|
|
|
|
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
--- ./dist/out.css ---
|
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
|
|
|
<EMPTY>
|
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 18:12:47 +01:00
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
// Use a CSS variable in JS/TS land, now it should be generated.
|
|
|
|
|
await fs.write(
|
|
|
|
|
'./src/index.ts',
|
|
|
|
|
ts`
|
|
|
|
|
function MyComponent() {
|
|
|
|
|
return <motion.div animate={{ backgroundColor: 'var(--color-blue-500)' }} />
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-20 12:52:02 +01:00
|
|
|
// prettier-ignore
|
|
|
|
|
await fs.expectFileToContain(
|
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 18:12:47 +01:00
|
|
|
'./dist/out.css',
|
|
|
|
|
css`
|
2025-03-20 12:52:02 +01:00
|
|
|
:root, :host {
|
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 18:12:47 +01:00
|
|
|
--color-blue-500: blue;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
)
|
2025-02-25 16:07:16 +01:00
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'can read files with UTF-8 files with BOM',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.css': withBOM(css`
|
|
|
|
|
@reference 'tailwindcss/theme.css';
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
`),
|
|
|
|
|
'index.html': withBOM(html`
|
|
|
|
|
<div class="underline"></div>
|
|
|
|
|
`),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, exec, expect }) => {
|
|
|
|
|
await exec('pnpm tailwindcss --input index.css --output dist/out.css')
|
|
|
|
|
|
|
|
|
|
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
--- ./dist/out.css ---
|
|
|
|
|
.underline {
|
|
|
|
|
text-decoration-line: underline;
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'fails when reading files with UTF-16 files with BOM',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ fs, exec, expect }) => {
|
|
|
|
|
await fs.write(
|
|
|
|
|
'index.css',
|
|
|
|
|
withBOM(css`
|
|
|
|
|
@reference 'tailwindcss/theme.css';
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
`),
|
|
|
|
|
'utf16le',
|
|
|
|
|
)
|
|
|
|
|
await fs.write(
|
|
|
|
|
'index.html',
|
|
|
|
|
withBOM(html`
|
|
|
|
|
<div class="underline"></div>
|
|
|
|
|
`),
|
|
|
|
|
'utf16le',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await expect(exec('pnpm tailwindcss --input index.css --output dist/out.css')).rejects.toThrow(
|
|
|
|
|
/Invalid declaration:/,
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-24 13:00:36 +02:00
|
|
|
test(
|
|
|
|
|
'fails when input file does not exist',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ exec, expect }) => {
|
|
|
|
|
await expect(exec('pnpm tailwindcss --input index.css --output dist/out.css')).rejects.toThrow(
|
|
|
|
|
/Specified input file.*does not exist./,
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'fails when input file and output file are the same',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'input.css': '',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ exec, expect }) => {
|
|
|
|
|
await expect(exec('pnpm tailwindcss --input input.css --output input.css')).rejects.toThrow(
|
|
|
|
|
/Specified input file.*and output file.*are identical./,
|
|
|
|
|
)
|
|
|
|
|
await expect(
|
|
|
|
|
exec('pnpm tailwindcss --input input.css --output ./src/../input.css'),
|
|
|
|
|
).rejects.toThrow(/Specified input file.*and output file.*are identical./)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
test(
|
|
|
|
|
'input and output flags can be the same if `-` is used',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.html': html`<div class="flex"></div>`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ exec, expect }) => {
|
|
|
|
|
expect(
|
|
|
|
|
await exec('pnpm tailwindcss --input - --output -', undefined, {
|
|
|
|
|
stdin: '@tailwind utilities;',
|
|
|
|
|
}),
|
|
|
|
|
).toContain(candidate`flex`)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-31 18:44:06 +02:00
|
|
|
test(
|
|
|
|
|
'changes to CSS files should pick up new CSS variables (if any)',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'unrelated.module.css': css`
|
|
|
|
|
.module {
|
|
|
|
|
color: var(--color-blue-500);
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.css': css`
|
|
|
|
|
@import 'tailwindcss/theme';
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
`,
|
|
|
|
|
'index.html': html`<div class="flex"></div>`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ spawn, exec, fs, expect }) => {
|
|
|
|
|
// Generate the initial build so output CSS files exist on disk
|
|
|
|
|
await exec('pnpm tailwindcss --input ./index.css --output ./dist/out.css')
|
|
|
|
|
|
|
|
|
|
// NOTE: We are writing to an output CSS file which is not being ignored by
|
|
|
|
|
// `.gitignore` nor marked with `@source not`. This should not result in an
|
|
|
|
|
// infinite loop.
|
|
|
|
|
let process = await spawn(
|
|
|
|
|
'pnpm tailwindcss --input ./index.css --output ./dist/out.css --watch',
|
|
|
|
|
)
|
|
|
|
|
await process.onStderr((m) => m.includes('Done in'))
|
|
|
|
|
|
|
|
|
|
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
--- ./dist/out.css ---
|
|
|
|
|
:root, :host {
|
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
|
|
|
--color-blue-500: oklch(62.3% 0.214 259.815);
|
2025-03-31 18:44:06 +02:00
|
|
|
}
|
|
|
|
|
.flex {
|
|
|
|
|
display: flex;
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
await fs.write(
|
|
|
|
|
'unrelated.module.css',
|
|
|
|
|
css`
|
|
|
|
|
.module {
|
|
|
|
|
color: var(--color-blue-500);
|
|
|
|
|
background-color: var(--color-red-500);
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
await process.onStderr((m) => m.includes('Done in'))
|
|
|
|
|
|
|
|
|
|
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
--- ./dist/out.css ---
|
|
|
|
|
:root, :host {
|
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
|
|
|
--color-red-500: oklch(63.7% 0.237 25.331);
|
|
|
|
|
--color-blue-500: oklch(62.3% 0.214 259.815);
|
2025-03-31 18:44:06 +02:00
|
|
|
}
|
|
|
|
|
.flex {
|
|
|
|
|
display: flex;
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2025-04-02 11:05:35 +02:00
|
|
|
test(
|
|
|
|
|
'polyfills should be imported after external `@import url(…)` statements',
|
|
|
|
|
{
|
|
|
|
|
fs: {
|
|
|
|
|
'package.json': json`
|
|
|
|
|
{
|
|
|
|
|
"dependencies": {
|
|
|
|
|
"tailwindcss": "workspace:^",
|
|
|
|
|
"@tailwindcss/cli": "workspace:^"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
'index.css': css`
|
|
|
|
|
@import url('https://fonts.googleapis.com');
|
|
|
|
|
@import 'tailwindcss';
|
|
|
|
|
`,
|
|
|
|
|
'index.html': html`<div class="bg-red-500/50 shadow-md"></div>`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
async ({ exec, fs, expect }) => {
|
|
|
|
|
await exec('pnpm tailwindcss --input ./index.css --output ./dist/out.css')
|
|
|
|
|
expect(await fs.dumpFiles('./dist/*.css')).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
--- ./dist/out.css ---
|
|
|
|
|
@import url('https://fonts.googleapis.com');
|
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:

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
|
|
|
@layer properties;
|
2025-04-02 11:05:35 +02:00
|
|
|
@layer theme, base, components, utilities;
|
|
|
|
|
@layer theme {
|
|
|
|
|
:root, :host {
|
|
|
|
|
--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;
|
|
|
|
|
--color-red-500: oklch(63.7% 0.237 25.331);
|
|
|
|
|
--default-font-family: var(--font-sans);
|
|
|
|
|
--default-mono-font-family: var(--font-mono);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@layer base {
|
|
|
|
|
*, ::after, ::before, ::backdrop, ::file-selector-button {
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
border: 0 solid;
|
|
|
|
|
}
|
|
|
|
|
html, :host {
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
-webkit-text-size-adjust: 100%;
|
|
|
|
|
tab-size: 4;
|
|
|
|
|
font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji');
|
|
|
|
|
font-feature-settings: var(--default-font-feature-settings, normal);
|
|
|
|
|
font-variation-settings: var(--default-font-variation-settings, normal);
|
|
|
|
|
-webkit-tap-highlight-color: transparent;
|
|
|
|
|
}
|
|
|
|
|
hr {
|
|
|
|
|
height: 0;
|
|
|
|
|
color: inherit;
|
|
|
|
|
border-top-width: 1px;
|
|
|
|
|
}
|
|
|
|
|
abbr:where([title]) {
|
|
|
|
|
-webkit-text-decoration: underline dotted;
|
|
|
|
|
text-decoration: underline dotted;
|
|
|
|
|
}
|
|
|
|
|
h1, h2, h3, h4, h5, h6 {
|
|
|
|
|
font-size: inherit;
|
|
|
|
|
font-weight: inherit;
|
|
|
|
|
}
|
|
|
|
|
a {
|
|
|
|
|
color: inherit;
|
|
|
|
|
-webkit-text-decoration: inherit;
|
|
|
|
|
text-decoration: inherit;
|
|
|
|
|
}
|
|
|
|
|
b, strong {
|
|
|
|
|
font-weight: bolder;
|
|
|
|
|
}
|
|
|
|
|
code, kbd, samp, pre {
|
|
|
|
|
font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace);
|
|
|
|
|
font-feature-settings: var(--default-mono-font-feature-settings, normal);
|
|
|
|
|
font-variation-settings: var(--default-mono-font-variation-settings, normal);
|
|
|
|
|
font-size: 1em;
|
|
|
|
|
}
|
|
|
|
|
small {
|
|
|
|
|
font-size: 80%;
|
|
|
|
|
}
|
|
|
|
|
sub, sup {
|
|
|
|
|
font-size: 75%;
|
|
|
|
|
line-height: 0;
|
|
|
|
|
position: relative;
|
|
|
|
|
vertical-align: baseline;
|
|
|
|
|
}
|
|
|
|
|
sub {
|
|
|
|
|
bottom: -0.25em;
|
|
|
|
|
}
|
|
|
|
|
sup {
|
|
|
|
|
top: -0.5em;
|
|
|
|
|
}
|
|
|
|
|
table {
|
|
|
|
|
text-indent: 0;
|
|
|
|
|
border-color: inherit;
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
}
|
|
|
|
|
:-moz-focusring {
|
|
|
|
|
outline: auto;
|
|
|
|
|
}
|
|
|
|
|
progress {
|
|
|
|
|
vertical-align: baseline;
|
|
|
|
|
}
|
|
|
|
|
summary {
|
|
|
|
|
display: list-item;
|
|
|
|
|
}
|
|
|
|
|
ol, ul, menu {
|
|
|
|
|
list-style: none;
|
|
|
|
|
}
|
|
|
|
|
img, svg, video, canvas, audio, iframe, embed, object {
|
|
|
|
|
display: block;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
}
|
|
|
|
|
img, video {
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
|
|
|
|
button, input, select, optgroup, textarea, ::file-selector-button {
|
|
|
|
|
font: inherit;
|
|
|
|
|
font-feature-settings: inherit;
|
|
|
|
|
font-variation-settings: inherit;
|
|
|
|
|
letter-spacing: inherit;
|
|
|
|
|
color: inherit;
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
:where(select:is([multiple], [size])) optgroup {
|
|
|
|
|
font-weight: bolder;
|
|
|
|
|
}
|
|
|
|
|
:where(select:is([multiple], [size])) optgroup option {
|
|
|
|
|
padding-inline-start: 20px;
|
|
|
|
|
}
|
|
|
|
|
::file-selector-button {
|
|
|
|
|
margin-inline-end: 4px;
|
|
|
|
|
}
|
|
|
|
|
::placeholder {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
|
|
|
|
|
::placeholder {
|
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 17:12:34 +02:00
|
|
|
color: currentcolor;
|
|
|
|
|
@supports (color: color-mix(in lab, red, red)) {
|
|
|
|
|
color: color-mix(in oklab, currentcolor 50%, transparent);
|
|
|
|
|
}
|
2025-04-02 11:05:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
textarea {
|
|
|
|
|
resize: vertical;
|
|
|
|
|
}
|
|
|
|
|
::-webkit-search-decoration {
|
|
|
|
|
-webkit-appearance: none;
|
|
|
|
|
}
|
|
|
|
|
::-webkit-date-and-time-value {
|
|
|
|
|
min-height: 1lh;
|
|
|
|
|
text-align: inherit;
|
|
|
|
|
}
|
|
|
|
|
::-webkit-datetime-edit {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
}
|
|
|
|
|
::-webkit-datetime-edit-fields-wrapper {
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
|
|
|
|
|
padding-block: 0;
|
|
|
|
|
}
|
2025-07-15 14:41:04 -04:00
|
|
|
::-webkit-calendar-picker-indicator {
|
|
|
|
|
line-height: 1;
|
|
|
|
|
}
|
2025-04-02 11:05:35 +02:00
|
|
|
:-moz-ui-invalid {
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
}
|
|
|
|
|
button, input:where([type='button'], [type='reset'], [type='submit']), ::file-selector-button {
|
|
|
|
|
appearance: button;
|
|
|
|
|
}
|
|
|
|
|
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
|
|
|
|
[hidden]:where(:not([hidden='until-found'])) {
|
|
|
|
|
display: none !important;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@layer utilities {
|
|
|
|
|
.bg-red-500\\/50 {
|
|
|
|
|
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 50%, transparent);
|
|
|
|
|
@supports (color: color-mix(in lab, red, red)) {
|
|
|
|
|
background-color: color-mix(in oklab, var(--color-red-500) 50%, transparent);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.shadow-md {
|
|
|
|
|
--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
|
|
|
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@property --tw-shadow {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: 0 0 #0000;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-shadow-color {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-shadow-alpha {
|
|
|
|
|
syntax: "<percentage>";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: 100%;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-inset-shadow {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: 0 0 #0000;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-inset-shadow-color {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-inset-shadow-alpha {
|
|
|
|
|
syntax: "<percentage>";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: 100%;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-ring-color {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-ring-shadow {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: 0 0 #0000;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-inset-ring-color {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-inset-ring-shadow {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: 0 0 #0000;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-ring-inset {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-ring-offset-width {
|
|
|
|
|
syntax: "<length>";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: 0px;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-ring-offset-color {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: #fff;
|
|
|
|
|
}
|
|
|
|
|
@property --tw-ring-offset-shadow {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: 0 0 #0000;
|
|
|
|
|
}
|
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:

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
|
|
|
@layer properties {
|
|
|
|
|
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
|
|
|
|
*, ::before, ::after, ::backdrop {
|
|
|
|
|
--tw-shadow: 0 0 #0000;
|
|
|
|
|
--tw-shadow-color: initial;
|
|
|
|
|
--tw-shadow-alpha: 100%;
|
|
|
|
|
--tw-inset-shadow: 0 0 #0000;
|
|
|
|
|
--tw-inset-shadow-color: initial;
|
|
|
|
|
--tw-inset-shadow-alpha: 100%;
|
|
|
|
|
--tw-ring-color: initial;
|
|
|
|
|
--tw-ring-shadow: 0 0 #0000;
|
|
|
|
|
--tw-inset-ring-color: initial;
|
|
|
|
|
--tw-inset-ring-shadow: 0 0 #0000;
|
|
|
|
|
--tw-ring-inset: initial;
|
|
|
|
|
--tw-ring-offset-width: 0px;
|
|
|
|
|
--tw-ring-offset-color: #fff;
|
|
|
|
|
--tw-ring-offset-shadow: 0 0 #0000;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-04-02 11:05:35 +02:00
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2025-02-25 16:07:16 +01:00
|
|
|
function withBOM(text: string): string {
|
|
|
|
|
return '\uFEFF' + text
|
|
|
|
|
}
|