Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
import dedent from 'dedent'
|
2025-04-03 17:07:38 +02:00
|
|
|
import { mkdir, mkdtemp, readFile, rm, unlink, writeFile } from 'node:fs/promises'
|
2025-03-31 15:26:01 +02:00
|
|
|
import { tmpdir } from 'node:os'
|
|
|
|
|
import path from 'path'
|
2024-03-05 14:23:26 +01:00
|
|
|
import postcss from 'postcss'
|
2025-03-31 15:26:01 +02:00
|
|
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
import { pretty } from '../../tailwindcss/src/test-utils/run'
|
2024-03-05 14:23:26 +01:00
|
|
|
import tailwindcss from './index'
|
|
|
|
|
|
|
|
|
|
// We give this file path to PostCSS for processing.
|
|
|
|
|
// This file doesn't exist, but the path is used to resolve imports.
|
|
|
|
|
// We place it in packages/ because Vitest runs in the monorepo root,
|
|
|
|
|
// and packages/tailwindcss must be a sub-folder for
|
|
|
|
|
// @import 'tailwindcss' to work.
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
function inputCssFilePath() {
|
|
|
|
|
// Including the current test name to ensure that the cache is invalidated per
|
|
|
|
|
// test otherwise the cache will be used across tests.
|
|
|
|
|
return `${__dirname}/fixtures/example-project/input.css?test=${expect.getState().currentTestName}`
|
|
|
|
|
}
|
2024-03-05 14:23:26 +01:00
|
|
|
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
const css = dedent
|
2024-03-05 14:23:26 +01:00
|
|
|
|
2026-07-06 16:32:20 +05:00
|
|
|
async function run(plugin: any, from: string, input: string): Promise<string> {
|
|
|
|
|
let ast = postcss.parse(input)
|
|
|
|
|
for (let runner of plugin.plugins) {
|
|
|
|
|
if (runner.Once) {
|
|
|
|
|
await runner.Once(ast, { postcss, result: { opts: { from }, messages: [] } })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ast.toString()
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-05 14:23:26 +01:00
|
|
|
test("`@import 'tailwindcss'` is replaced with the generated CSS", async () => {
|
2024-03-08 18:36:07 +01:00
|
|
|
let processor = postcss([
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
])
|
2024-03-05 14:23:26 +01:00
|
|
|
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
let result = await processor.process(`@import 'tailwindcss'`, { from: inputCssFilePath() })
|
2024-03-05 14:23:26 +01:00
|
|
|
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(result.css)).toMatchSnapshot()
|
2024-03-05 14:23:26 +01:00
|
|
|
|
|
|
|
|
// Check for dependency messages
|
|
|
|
|
expect(result.messages).toContainEqual({
|
|
|
|
|
type: 'dependency',
|
|
|
|
|
file: expect.stringMatching(/index.html$/g),
|
|
|
|
|
parent: expect.any(String),
|
|
|
|
|
plugin: expect.any(String),
|
|
|
|
|
})
|
|
|
|
|
expect(result.messages).toContainEqual({
|
|
|
|
|
type: 'dependency',
|
|
|
|
|
file: expect.stringMatching(/index.js$/g),
|
|
|
|
|
parent: expect.any(String),
|
|
|
|
|
plugin: expect.any(String),
|
|
|
|
|
})
|
|
|
|
|
expect(result.messages).toContainEqual({
|
|
|
|
|
type: 'dir-dependency',
|
2026-03-15 22:04:58 +01:00
|
|
|
dir: expect.stringMatching(/example-project[/|\\]src$/g),
|
2024-03-05 14:23:26 +01:00
|
|
|
glob: expect.stringMatching(/^\*\*\/\*/g),
|
|
|
|
|
parent: expect.any(String),
|
|
|
|
|
plugin: expect.any(String),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('output is optimized by Lightning CSS', async () => {
|
2024-03-08 18:36:07 +01:00
|
|
|
let processor = postcss([
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
])
|
2024-03-05 14:23:26 +01:00
|
|
|
|
|
|
|
|
let result = await processor.process(
|
|
|
|
|
css`
|
|
|
|
|
@layer utilities {
|
|
|
|
|
.foo {
|
|
|
|
|
@apply text-[black];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@layer utilities {
|
|
|
|
|
.bar {
|
|
|
|
|
color: red;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
{ from: inputCssFilePath() },
|
2024-03-05 14:23:26 +01:00
|
|
|
)
|
|
|
|
|
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(result.css)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
@layer utilities {
|
2024-03-05 14:23:26 +01:00
|
|
|
.foo {
|
|
|
|
|
color: #000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bar {
|
|
|
|
|
color: red;
|
|
|
|
|
}
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
2024-03-05 14:23:26 +01:00
|
|
|
`)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('@apply can be used without emitting the theme in the CSS file', async () => {
|
2024-03-08 18:36:07 +01:00
|
|
|
let processor = postcss([
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
])
|
2024-03-05 14:23:26 +01:00
|
|
|
|
|
|
|
|
let result = await processor.process(
|
|
|
|
|
css`
|
2025-02-25 11:36:43 +01:00
|
|
|
@reference 'tailwindcss/theme.css';
|
2024-03-05 14:23:26 +01:00
|
|
|
.foo {
|
|
|
|
|
@apply text-red-500;
|
|
|
|
|
}
|
|
|
|
|
`,
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
{ from: inputCssFilePath() },
|
2024-03-05 14:23:26 +01:00
|
|
|
)
|
|
|
|
|
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(result.css)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
.foo {
|
Improve compatibility with Safari 15 (#17435)
This PR improves the compatibility with Tailwind CSS v4 with unsupported
browsers with the goal to greatly improve compatibility with Safari 15.
To make this work, this PR makes the following changes to all code
- Change `oklab(…)` default theme values to use a percentage in the
first place (so instead of `--color-red-500: oklch(0.637 0.237 25.331);`
we now define it as `--color-red-500: oklch(63.7% 0.237 25.331);` since
this syntax has much broader support on Safari).
- Polyfill `@property` with a `@supports` query targeting older versions
of Safari and Firefox *
- Create fallbacks for the `color-mix(…)` function that use _inlined
color values from your theme_ so that they can be computed a compile
time by `lightningcss`. These fallbacks will convert to srgb to increase
compatibility.
- Create fallbacks for the _relative color_ feature used in the new
shadow utilities and using `color-mix(…)` in case _relative color_ is
applied on `currentcolor` (due to limited browser support)
- Create fallbacks for gradient interpolation methods (e.g. to support
`bg-linear-to-r/oklab`)
- Polyfill `@media` queries range syntax.
## A simplified example
Given this example CSS input:
```css
@import 'tailwindcss';
@source inline('from-cyan-500/50 bg-linear-45');
```
Here's the updated output CSS including the newly added polyfills and
updated `oklab` values:
```css
.bg-linear-45 {
--tw-gradient-position: 45deg;
background-image: linear-gradient(var(--tw-gradient-stops));
}
@supports (background-image: linear-gradient(in lab, red, red)) {
.bg-linear-45 {
--tw-gradient-position: 45deg in oklab;
}
}
.from-cyan-500\\/50 {
--tw-gradient-from: oklab(71.5% -.11682 -.08247 / .5);
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
}
@supports (color: color-mix(in lab, red, red)) {
.from-cyan-500\\/50 {
--tw-gradient-from: color-mix(in oklab, var(--color-cyan-500) 50%, transparent);
}
}
:root, :host {
--color-cyan-500: oklch(71.5% .143 215.221);
}
@supports (((-webkit-hyphens: none)) and (not (margin-trim: 1lh))) or ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
@layer base {
*, :before, :after, ::backdrop {
--tw-gradient-position: initial;
--tw-gradient-from: #0000;
--tw-gradient-via: #0000;
--tw-gradient-to: #0000;
--tw-gradient-stops: initial;
--tw-gradient-via-stops: initial;
--tw-gradient-from-position: 0%;
--tw-gradient-via-position: 50%;
--tw-gradient-to-position: 100%;
}
}
}
@property --tw-gradient-position {
syntax: "*";
inherits: false
}
@property --tw-gradient-from {
syntax: "<color>";
inherits: false;
initial-value: #0000;
}
@property --tw-gradient-via {
syntax: "<color>";
inherits: false;
initial-value: #0000;
}
@property --tw-gradient-to {
syntax: "<color>";
inherits: false;
initial-value: #0000;
}
@property --tw-gradient-stops {
syntax: "*";
inherits: false
}
@property --tw-gradient-via-stops {
syntax: "*";
inherits: false
}
@property --tw-gradient-from-position {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0%;
}
@property --tw-gradient-via-position {
syntax: "<length-percentage>";
inherits: false;
initial-value: 50%;
}
@property --tw-gradient-to-position {
syntax: "<length-percentage>";
inherits: false;
initial-value: 100%;
}
```
## \* A note on `@property` polyfills and CSS modules
On Next.js, CSS module files are required to be _pure_, meaning that all
selectors must either be scoped to a class or an ID. Fortunatnyl for us,
this does not apply to `@property` rules which we've been using before
to initialize CSS variables.
However, since we're now bringing back the `@property` polyfills, that
would cause unexpected rules to be exported from the CSS file as this:
```css
@reference "tailwindcss";
.skew {
@apply skew-7;
}
```
Would turn to the following file:
```css
.skew {
/* … */
}
@supports (/*…*/) {
@layer base {
*, :before, :after, ::backdrop {
--tw-gradient-position: initial;
}
}
}
@property /* … */
```
Notice that this adds a `*` selector which is not considered pure.
Unfortunately there is no way for us to silence this warning or work
around it, as the dependency causing this errors
([`postcss-modules-local-by-default`](https://github.com/css-modules/postcss-modules-local-by-default))
is bundled into Next.js. To work around crashes, these polyfills will
not apply to CSS modules processed by the PostCSS extension for now.
## Testing on tailwindcss.com
To see the changes in effect, take a look at this screencast that
compares tailwindcss.com on iOS 15.5 with a version that has the patches
of this PR applied:
https://github.com/user-attachments/assets/1279d6f5-3c63-4f30-839c-198a789f4292
## Test plan
- Tested on tailwindcss.com via a preview build:
https://tailwindcss-com-git-legacy-browsers-tailwindlabs.vercel.app/
- Updated tests
- Ensure we also test on Chrome 111, Safari 16.4, Firefox 128 to
make sure we have no regressions. Also tested on Safari 16.4, 15.5, 18.0
2025-04-01 13:33:22 +02:00
|
|
|
color: var(--color-red-500, oklch(63.7% .237 25.331));
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
2024-03-05 14:23:26 +01:00
|
|
|
`)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('processing without specifying a base path', () => {
|
2025-03-31 15:26:01 +02:00
|
|
|
let filepath: string
|
|
|
|
|
let dir: string
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
dir = await mkdtemp(path.join(tmpdir(), 'tw-postcss'))
|
|
|
|
|
await mkdir(dir, { recursive: true })
|
|
|
|
|
filepath = path.join(dir, 'my-test-file.html')
|
|
|
|
|
await writeFile(filepath, `<div class="md:[&:hover]:content-['testing_default_base_path']">`)
|
|
|
|
|
})
|
2024-03-05 14:23:26 +01:00
|
|
|
afterEach(() => unlink(filepath))
|
|
|
|
|
|
|
|
|
|
test('the current working directory is used by default', async () => {
|
2026-04-30 23:32:37 +02:00
|
|
|
using spy = vi.spyOn(process, 'cwd')
|
2025-03-31 15:26:01 +02:00
|
|
|
spy.mockReturnValue(dir)
|
|
|
|
|
|
2024-03-08 18:36:07 +01:00
|
|
|
let processor = postcss([tailwindcss({ optimize: { minify: false } })])
|
2024-03-05 14:23:26 +01:00
|
|
|
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
let result = await processor.process(`@import "tailwindcss"`, { from: inputCssFilePath() })
|
2024-03-05 14:23:26 +01:00
|
|
|
|
|
|
|
|
expect(result.css).toContain(
|
|
|
|
|
".md\\:\\[\\&\\:hover\\]\\:content-\\[\\'testing_default_base_path\\'\\]",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
expect(result.messages).toContainEqual({
|
|
|
|
|
type: 'dependency',
|
|
|
|
|
file: expect.stringMatching(/my-test-file.html$/g),
|
|
|
|
|
parent: expect.any(String),
|
|
|
|
|
plugin: expect.any(String),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
2024-07-11 09:47:26 -04:00
|
|
|
|
Fall back to the plugin `base` when PostCSS has no `from` option (#19980)
## Summary
`@tailwindcss/postcss` derives `inputBasePath` from `result.opts.from`:
```ts
let inputFile = result.opts.from ?? ''
let inputBasePath = path.dirname(path.resolve(inputFile))
```
When PostCSS calls the plugin without `from` (some bundlers, including
Turbopack, do this for certain CSS inputs), `inputFile` is `''`,
`path.resolve('')` returns `process.cwd()`, and `path.dirname(...)`
therefore returns the **parent of CWD**. The downstream `compileAst({
base: inputBasePath })` call then asks the resolver to find
`tailwindcss` from one level above the project root, which fails with:
```
Can't resolve 'tailwindcss' in '<parent of CWD>'
```
The plugin already computes `base = opts.base ?? process.cwd()` near the
top. Reusing that as the fallback gives a sensible default (CWD) and
respects an explicit `opts.base` when set.
```diff
-let inputBasePath = path.dirname(path.resolve(inputFile))
+let inputBasePath = inputFile
+ ? path.dirname(path.resolve(inputFile))
+ : base
```
## Test plan
Added a test in `packages/@tailwindcss-postcss/src/index.test.ts` that
processes `@import 'tailwindcss'` via `processor.process(input)` with no
`from` option. Before the fix, this throws `Error: Can't resolve
'tailwindcss' in '<parent of CWD>'`; after the fix, the import resolves
and the processor returns non-empty CSS.
I wasn't able to run the suite locally — `pnpm build` requires `cargo`
for `@tailwindcss/oxide` and I don't have a Rust toolchain set up — so
the test has been written to match existing conventions in
`index.test.ts` (vitest, plain
`postcss([tailwindcss({...})]).process(...)`), and I'm relying on CI to
verify.
---------
Co-authored-by: rebasecase <rebasecase@localhost>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-04-26 14:45:39 +01:00
|
|
|
test('fallback to `base` directory when `result.opts.from` is not provided', async () => {
|
2026-05-06 11:10:17 +02:00
|
|
|
using _ = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
|
|
|
|
Fall back to the plugin `base` when PostCSS has no `from` option (#19980)
## Summary
`@tailwindcss/postcss` derives `inputBasePath` from `result.opts.from`:
```ts
let inputFile = result.opts.from ?? ''
let inputBasePath = path.dirname(path.resolve(inputFile))
```
When PostCSS calls the plugin without `from` (some bundlers, including
Turbopack, do this for certain CSS inputs), `inputFile` is `''`,
`path.resolve('')` returns `process.cwd()`, and `path.dirname(...)`
therefore returns the **parent of CWD**. The downstream `compileAst({
base: inputBasePath })` call then asks the resolver to find
`tailwindcss` from one level above the project root, which fails with:
```
Can't resolve 'tailwindcss' in '<parent of CWD>'
```
The plugin already computes `base = opts.base ?? process.cwd()` near the
top. Reusing that as the fallback gives a sensible default (CWD) and
respects an explicit `opts.base` when set.
```diff
-let inputBasePath = path.dirname(path.resolve(inputFile))
+let inputBasePath = inputFile
+ ? path.dirname(path.resolve(inputFile))
+ : base
```
## Test plan
Added a test in `packages/@tailwindcss-postcss/src/index.test.ts` that
processes `@import 'tailwindcss'` via `processor.process(input)` with no
`from` option. Before the fix, this throws `Error: Can't resolve
'tailwindcss' in '<parent of CWD>'`; after the fix, the import resolves
and the processor returns non-empty CSS.
I wasn't able to run the suite locally — `pnpm build` requires `cargo`
for `@tailwindcss/oxide` and I don't have a Rust toolchain set up — so
the test has been written to match existing conventions in
`index.test.ts` (vitest, plain
`postcss([tailwindcss({...})]).process(...)`), and I'm relying on CI to
verify.
---------
Co-authored-by: rebasecase <rebasecase@localhost>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2026-04-26 14:45:39 +01:00
|
|
|
let processor = postcss([
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
let result = await processor.process(`@import 'tailwindcss'`)
|
|
|
|
|
|
|
|
|
|
expect(result.css.length).toBeGreaterThan(0)
|
|
|
|
|
})
|
|
|
|
|
|
2024-07-11 09:47:26 -04:00
|
|
|
describe('plugins', () => {
|
|
|
|
|
test('local CJS plugin', async () => {
|
|
|
|
|
let processor = postcss([
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
let result = await processor.process(
|
|
|
|
|
css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
2024-08-07 16:38:44 +02:00
|
|
|
@plugin './plugin.js';
|
2024-07-11 09:47:26 -04:00
|
|
|
`,
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
{ from: inputCssFilePath() },
|
2024-07-11 09:47:26 -04:00
|
|
|
)
|
|
|
|
|
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(result.css)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
.underline {
|
2024-08-07 16:38:44 +02:00
|
|
|
text-decoration-line: underline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (inverted-colors: inverted) {
|
|
|
|
|
.inverted\\:flex {
|
|
|
|
|
display: flex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hocus\\:underline:focus, .hocus\\:underline:hover {
|
|
|
|
|
text-decoration-line: underline;
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
2024-08-07 16:38:44 +02:00
|
|
|
`)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('local CJS plugin from `@import`-ed file', async () => {
|
|
|
|
|
let processor = postcss([
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
let result = await processor.process(
|
|
|
|
|
css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
|
|
|
|
@import '../example-project/src/relative-import.css';
|
|
|
|
|
`,
|
|
|
|
|
{ from: `${__dirname}/fixtures/another-project/input.css` },
|
|
|
|
|
)
|
|
|
|
|
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(result.css)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
.underline {
|
2024-07-11 09:47:26 -04:00
|
|
|
text-decoration-line: underline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (inverted-colors: inverted) {
|
|
|
|
|
.inverted\\:flex {
|
|
|
|
|
display: flex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hocus\\:underline:focus, .hocus\\:underline:hover {
|
|
|
|
|
text-decoration-line: underline;
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
2024-07-11 09:47:26 -04:00
|
|
|
`)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('published CJS plugin', async () => {
|
|
|
|
|
let processor = postcss([
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
let result = await processor.process(
|
|
|
|
|
css`
|
|
|
|
|
@import 'tailwindcss/utilities';
|
2024-07-29 16:58:07 +02:00
|
|
|
@plugin 'internal-example-plugin';
|
2024-07-11 09:47:26 -04:00
|
|
|
`,
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
{ from: inputCssFilePath() },
|
2024-07-11 09:47:26 -04:00
|
|
|
)
|
|
|
|
|
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(result.css)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
.underline {
|
2024-07-11 09:47:26 -04:00
|
|
|
text-decoration-line: underline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (inverted-colors: inverted) {
|
|
|
|
|
.inverted\\:flex {
|
|
|
|
|
display: flex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hocus\\:underline:focus, .hocus\\:underline:hover {
|
|
|
|
|
text-decoration-line: underline;
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
2024-07-11 09:47:26 -04:00
|
|
|
`)
|
|
|
|
|
})
|
|
|
|
|
})
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
|
|
|
|
|
test('bail early when Tailwind is not used', async () => {
|
|
|
|
|
let processor = postcss([
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
let result = await processor.process(
|
|
|
|
|
css`
|
|
|
|
|
.custom-css {
|
|
|
|
|
color: red;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
{ from: inputCssFilePath() },
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// `fixtures/example-project` includes an `underline` candidate. But since we
|
|
|
|
|
// didn't use `@tailwind utilities` we didn't scan for utilities.
|
|
|
|
|
expect(result.css).not.toContain('.underline {')
|
|
|
|
|
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(result.css)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
.custom-css {
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
color: red;
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
Improve performance of `@tailwindcss/postcss` and `@tailwindcss/vite` (#15226)
This PR improves the performance of the `@tailwindcss/postcss` and
`@tailwindcss/vite` implementations.
The issue is that in some scenarios, if you have multiple `.css` files,
then all of the CSS files are ran through the Tailwind CSS compiler. The
issue with this is that in a lot of cases, the CSS files aren't even
related to Tailwind CSS at all.
E.g.: in a Next.js project, if you use the `next/font/local` tool, then
every font you used will be in a separate CSS file. This means that we
run Tailwind CSS in all these files as well.
That said, running Tailwind CSS on these files isn't the end of the
world because we still need to handle `@import` in case `@tailwind
utilities` is being used. However, we also run the auto source detection
logic for every CSS file in the system. This part is bad.
To solve this, this PR introduces an internal `features` to collect what
CSS features are used throughout the system (`@import`, `@plugin`,
`@apply`, `@tailwind utilities`, etc…)
The `@tailwindcss/postcss` and `@tailwindcss/vite` plugin can use that
information to decide if they can take some shortcuts or not.
---
Overall, this means that we don't run the slow parts of Tailwind CSS if
we don't need to.
---------
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-29 16:59:29 +01:00
|
|
|
`)
|
|
|
|
|
})
|
2024-12-03 10:28:51 +01:00
|
|
|
|
2025-01-30 16:29:08 +01:00
|
|
|
test('handle CSS when only using a `@reference` (we should not bail early)', async () => {
|
|
|
|
|
let processor = postcss([
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
let result = await processor.process(
|
|
|
|
|
css`
|
|
|
|
|
@reference "tailwindcss/theme.css";
|
|
|
|
|
|
|
|
|
|
.foo {
|
|
|
|
|
@variant md {
|
|
|
|
|
bar: baz;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
{ from: inputCssFilePath() },
|
|
|
|
|
)
|
|
|
|
|
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(result.css)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
@media (min-width: 48rem) {
|
2025-01-30 16:29:08 +01:00
|
|
|
.foo {
|
|
|
|
|
bar: baz;
|
|
|
|
|
}
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
2025-01-30 16:29:08 +01:00
|
|
|
`)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('handle CSS when using a `@variant` using variants that do not rely on the `@theme`', async () => {
|
|
|
|
|
let processor = postcss([
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
let result = await processor.process(
|
|
|
|
|
css`
|
|
|
|
|
.foo {
|
|
|
|
|
@variant data-is-hoverable {
|
|
|
|
|
bar: baz;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
{ from: inputCssFilePath() },
|
|
|
|
|
)
|
|
|
|
|
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(result.css)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
.foo[data-is-hoverable] {
|
2025-01-30 16:29:08 +01:00
|
|
|
bar: baz;
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
2025-01-30 16:29:08 +01:00
|
|
|
`)
|
|
|
|
|
})
|
|
|
|
|
|
2024-12-03 10:28:51 +01:00
|
|
|
test('runs `Once` plugins in the right order', async () => {
|
|
|
|
|
let before = ''
|
|
|
|
|
let after = ''
|
|
|
|
|
let processor = postcss([
|
|
|
|
|
{
|
|
|
|
|
postcssPlugin: 'before',
|
|
|
|
|
Once(root) {
|
|
|
|
|
before = root.toString()
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
{
|
|
|
|
|
postcssPlugin: 'after',
|
|
|
|
|
Once(root) {
|
|
|
|
|
after = root.toString()
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
let result = await processor.process(
|
|
|
|
|
css`
|
|
|
|
|
@theme {
|
|
|
|
|
--color-red-500: red;
|
|
|
|
|
}
|
|
|
|
|
.custom-css {
|
|
|
|
|
color: theme(--color-red-500);
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
{ from: inputCssFilePath() },
|
|
|
|
|
)
|
|
|
|
|
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(result.css)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
.custom-css {
|
2024-12-03 10:28:51 +01:00
|
|
|
color: red;
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
2024-12-03 10:28:51 +01:00
|
|
|
`)
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(before)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
@theme {
|
2024-12-03 10:28:51 +01:00
|
|
|
--color-red-500: red;
|
|
|
|
|
}
|
|
|
|
|
.custom-css {
|
|
|
|
|
color: theme(--color-red-500);
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
2024-12-03 10:28:51 +01:00
|
|
|
`)
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(after)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
.custom-css {
|
2024-12-03 10:28:51 +01:00
|
|
|
color: red;
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
2024-12-03 10:28:51 +01:00
|
|
|
`)
|
|
|
|
|
})
|
2025-04-03 17:07:38 +02:00
|
|
|
|
|
|
|
|
describe('concurrent builds', () => {
|
|
|
|
|
let dir: string
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
dir = await mkdtemp(path.join(tmpdir(), 'tw-postcss'))
|
|
|
|
|
await writeFile(path.join(dir, 'index.html'), `<div class="underline"></div>`)
|
|
|
|
|
await writeFile(
|
|
|
|
|
path.join(dir, 'index.css'),
|
|
|
|
|
css`
|
|
|
|
|
@import './dependency.css';
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
await writeFile(
|
|
|
|
|
path.join(dir, 'dependency.css'),
|
|
|
|
|
css`
|
|
|
|
|
@tailwind utilities;
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
afterEach(() => rm(dir, { recursive: true, force: true }))
|
|
|
|
|
|
2025-04-03 17:24:26 +02:00
|
|
|
test('does experience a race-condition when calling the plugin two times for the same change', async () => {
|
2026-04-30 23:32:37 +02:00
|
|
|
using spy = vi.spyOn(process, 'cwd')
|
2025-04-03 17:07:38 +02:00
|
|
|
spy.mockReturnValue(dir)
|
|
|
|
|
|
|
|
|
|
let from = path.join(dir, 'index.css')
|
|
|
|
|
let input = (await readFile(path.join(dir, 'index.css'))).toString()
|
|
|
|
|
|
|
|
|
|
let plugin = tailwindcss({ optimize: { minify: false } })
|
|
|
|
|
|
2026-07-06 16:32:20 +05:00
|
|
|
let result = await run(plugin, from, input)
|
2025-04-03 17:07:38 +02:00
|
|
|
|
|
|
|
|
expect(result).toContain('.underline')
|
|
|
|
|
|
2025-04-03 17:24:26 +02:00
|
|
|
// Ensure that the mtime is updated
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
2025-04-03 17:07:38 +02:00
|
|
|
await writeFile(
|
|
|
|
|
path.join(dir, 'dependency.css'),
|
|
|
|
|
css`
|
|
|
|
|
@tailwind utilities;
|
|
|
|
|
.red {
|
|
|
|
|
color: red;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
|
2026-07-06 16:32:20 +05:00
|
|
|
let promise1 = run(plugin, from, input)
|
|
|
|
|
let promise2 = run(plugin, from, input)
|
2025-04-03 17:07:38 +02:00
|
|
|
|
|
|
|
|
expect(await promise1).toContain('.red')
|
|
|
|
|
expect(await promise2).toContain('.red')
|
|
|
|
|
})
|
|
|
|
|
})
|
2025-04-04 14:58:50 +02:00
|
|
|
|
2026-07-06 16:32:20 +05:00
|
|
|
test('rebuilds when the input CSS changes even if the `from` file on disk did not', async () => {
|
|
|
|
|
let dir = await mkdtemp(path.join(tmpdir(), 'tw-postcss'))
|
|
|
|
|
// The `from` file exists on disk with a stable mtime. The input CSS is handed
|
|
|
|
|
// to the plugin directly (as a preprocessor like Sass would produce it), so it
|
|
|
|
|
// can change without `from`'s mtime ever changing.
|
|
|
|
|
await writeFile(path.join(dir, 'index.css'), '')
|
|
|
|
|
|
|
|
|
|
let from = path.join(dir, 'index.css')
|
|
|
|
|
let plugin = tailwindcss({ base: dir, optimize: { minify: false } })
|
|
|
|
|
|
|
|
|
|
let first = await run(
|
|
|
|
|
plugin,
|
|
|
|
|
from,
|
|
|
|
|
css`
|
|
|
|
|
@tailwind utilities;
|
|
|
|
|
.first {
|
|
|
|
|
@apply underline;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
let second = await run(
|
|
|
|
|
plugin,
|
|
|
|
|
from,
|
|
|
|
|
css`
|
|
|
|
|
@tailwind utilities;
|
|
|
|
|
.second {
|
|
|
|
|
@apply line-through;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
expect(first).toContain('.first')
|
|
|
|
|
expect(second).toContain('.second')
|
|
|
|
|
|
|
|
|
|
await rm(dir, { recursive: true, force: true })
|
|
|
|
|
})
|
|
|
|
|
|
2025-04-04 14:58:50 +02:00
|
|
|
test('does not register the input file as a dependency, even if it is passed in as relative path', async () => {
|
|
|
|
|
let processor = postcss([
|
|
|
|
|
tailwindcss({ base: `${__dirname}/fixtures/example-project`, optimize: { minify: false } }),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
let result = await processor.process(`@tailwind utilities`, { from: './input.css' })
|
|
|
|
|
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
expect(pretty(result.css)).toMatchInlineSnapshot(`
|
|
|
|
|
"
|
|
|
|
|
.underline {
|
2025-04-04 14:58:50 +02:00
|
|
|
text-decoration-line: underline;
|
Improve snapshot tests (#20013)
This PR improves the snapshot tests. At first, this looks like a very
silly PR, but I swear I have legit reasons for these changes:
First, there are a few places where we use `.toMatchInlineSnapshot()`
for tests where we expect that nothing is being generated. This is fine,
but the issue is that this means that if you're not careful, and if we
have a bug, then these snapshots could start producing something.
Updating these snapshots is _too_ easy. So instead, we convert them to
an explicit `.toEqual('')`
Next, I introduced a `pretty` helper function which is used behind the
scenes in the `compileCss(…)`, `run(…)`, and `optimizeCss(…)` test
helpers. It's very silly and simple, it either returns `''` when the
trimmed input is empty, or it will wrap the result in `\n…\n`. The
reason for this is because of how (inline) snapshots works in Vitest. A
snapshot result will be in double quotes, and inside backticks:
```ts
expect(result.css.trim()).toMatchInlineSnapshot(`
"@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}"
`)
```
This CSS now starts with `"` and ends with `"`. While that is fine, it
starts to get annoying when we have merge conflicts when CSS changes
(this is what triggered me to make this PR because I ran into this a
dozen times already). Because the first and last CSS line also contain a
`"` that you have to keep into account.
Instead we now use `\n` around the output, which makes the tests look
like this:
```ts
expect(pretty(result.css)).toMatchInlineSnapshot(`
"
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
"
`)
```
In a perfect world, I wish we could use something like:
```css
expect(pretty(result.css)).toMatchInlineSnapshot(css`
@layer utilities {
.foo {
color: #000;
}
.bar {
color: red;
}
}
`)
```
That way you are only dealing with CSS, nothing else. Most editors will
show syntax highlighting, and even prettier will do formatting on the
CSS to keep everything consistent.
Unfortunately this also causes issues because when prettier formats
this, then the input/output will not always match. We can solve that by
parsing both sides and compare the ASTs but that would make things
slower.
The biggest issue is that Vitest doesn't support this. You can use
custom serializers
https://vitest.dev/guide/snapshot.html#custom-snapshot-matchers and
domains https://vitest.dev/guide/snapshot.html#custom-snapshot-domain
but this has an annoying issue around escaping values.
When you use `css` it's typically implemented as `const css =
String.raw`, which means that you can write actual CSS instead of JS:
```ts
let input = css`
.\[color:red\] {
color: red;
}
`
```
But vitest would double scape the `\`, which would make the snapshot
test fail:
```ts
let input = css`
.\\[color:red\\] {
color: red;
}
`
```
**Edit**: It is possible with a custom snapshot environment! But this
still introduces some levels of indirection. We are also not really
testing the same thing anymore. Prettier will be formatting the CSS, we
rely on our own CSS parser / printer, which is fine but subtle bugs
could maybe be invisible or maybe it unlocks some hidden bugs, who
knows. Funnily enough, running tests with this new matcher also goes
from `23.60s` to `22.88s` (just 1 run comparison).
<img width="1227" height="897" alt="image"
src="https://github.com/user-attachments/assets/698a0c67-a1fc-46e3-ba5a-60e5873b32df"
/>
Long story short, simple `\n` and `\n` boundaries it is!
## Test plan
- Everything still works as expected.
- There are visual changes in tests, but no actual source code was
updated either so there can not be an accidental diff
2026-05-05 22:24:25 +02:00
|
|
|
}
|
|
|
|
|
"
|
2025-04-04 14:58:50 +02:00
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
// Check for dependency messages
|
|
|
|
|
expect(result.messages).not.toContainEqual({
|
|
|
|
|
type: 'dependency',
|
|
|
|
|
file: expect.stringMatching(/input.css$/g),
|
|
|
|
|
parent: expect.any(String),
|
|
|
|
|
plugin: expect.any(String),
|
|
|
|
|
})
|
|
|
|
|
})
|