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}`),
|
|
|
|
|
],
|
|
|
|
|
])('%s', (_, command) => {
|
|
|
|
|
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']`,
|
|
|
|
|
])
|
|
|
|
|
},
|
|
|
|
|
)
|
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`
|
|
|
|
|
@import 'tailwindcss/theme' theme(reference);
|
|
|
|
|
|
|
|
|
|
/* (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 */
|
|
|
|
|
/* - './page/ignored.html' should be ignored */
|
|
|
|
|
@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"]',
|
|
|
|
|
'pages/ignored.html': 'content-["pages/ignored.html"] content-["BAD"]',
|
|
|
|
|
'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 ---
|
|
|
|
|
.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);
|
|
|
|
|
}
|
|
|
|
|
.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: "";
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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`
|
|
|
|
|
@import 'tailwindcss/theme' theme(reference);
|
|
|
|
|
|
|
|
|
|
/* 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
|
|
|
|
|
`,
|
|
|
|
|
|
|
|
|
|
// Project D, foo.html is ignored by the gitignore file.
|
|
|
|
|
'project-d/src/foo.html': html`
|
|
|
|
|
<div
|
|
|
|
|
class="content-['SHOULD-NOT-EXIST-IN-OUTPUT'] content-['project-d/src/foo.html']"
|
|
|
|
|
></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 ---
|
|
|
|
|
.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);
|
|
|
|
|
}
|
|
|
|
|
.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: "";
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
// 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`
|
|
|
|
|
@import 'tailwindcss/theme' theme(reference);
|
|
|
|
|
|
|
|
|
|
/* (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(none);
|
|
|
|
|
|
|
|
|
|
/* (2) */
|
|
|
|
|
/* - './pages' should be auto-scanned */
|
|
|
|
|
/* - Only '.html' files should be included */
|
|
|
|
|
/* - './page/ignored.html' should be ignored */
|
|
|
|
|
@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"]',
|
|
|
|
|
'pages/ignored.html': 'content-["pages/ignored.html"] content-["BAD"]',
|
|
|
|
|
'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 ---
|
|
|
|
|
.content-\\[\\"pages\\/foo\\.html\\"\\] {
|
|
|
|
|
--tw-content: "pages/foo.html";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
.content-\\[\\"pages\\/nested\\/foo\\.html\\"\\] {
|
|
|
|
|
--tw-content: "pages/nested/foo.html";
|
|
|
|
|
content: var(--tw-content);
|
|
|
|
|
}
|
|
|
|
|
@property --tw-content {
|
|
|
|
|
syntax: "*";
|
|
|
|
|
inherits: false;
|
|
|
|
|
initial-value: "";
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
`)
|
|
|
|
|
},
|
|
|
|
|
)
|
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 ---
|
2025-02-10 13:26:13 +01:00
|
|
|
:root, :host {
|
|
|
|
|
--color-blue-500: blue;
|
|
|
|
|
}
|
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)' }} />
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
fs.expectFileToContain(
|
|
|
|
|
'./dist/out.css',
|
|
|
|
|
css`
|
|
|
|
|
:root,
|
|
|
|
|
:host {
|
|
|
|
|
--color-blue-500: blue;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
)
|