Ignore @tailwind utilities inside @reference (#17836)
You can use `@reference "tailwindcss"` or `@reference
"../path/to/your/css/file.css"` to reference your theme for use in
`@apply`, `theme(…)`, etc…
Unfortunatley, because the imported file still contains `@tailwind
utilities` it would trigger a re-scan of the filesystem — even though
the use of `@reference` ensures that no CSS can actually be output by
the import itself.
This PR does two things:
- Adds some explicit feature detection tests for what features we pick
up in a stylesheet based on the CSS written and how things are imported
- Explicitly ignores `@tailwind utilities` inside of `@reference` so it
isn't a trigger for file scanning
Because of how Vite itself handles dependencies editing files on disk
will still trigger a rebuild of any file using `@reference`. This is
because Vite rebuilds files when _any_ of its transitive dependencies
change.
For example, given this Vue file:
```vue
<style>
@reference "./styles.css";
</style>
<template> <!-- ... --> </template>
```
And this stylesheet:
```css
@import "tailwindcss";
```
The dependency chain looks like this: `file.vue -> styles.css -> {all
the sources in your project}`
Vite sees that a file (e.g. `index.html`) has changed, thus `styles.css`
needs change, which means `file.vue` needs to be compiled again as well.
Now in reality we depend on the _on disk_ version of styles.css not the
compiled version but Vite itself doesn't know that (or have a way to
indicate this afaik).
Coming up with a solution to that problem will have to be a separate PR
— but there is a workaround:
### 1. Inline the imports from `@import "tailwindcss";`
Replace this in your main stylesheet:
```css
@import "tailwindcss";
```
with this (this is basically what `node_modules/tailwindcss/index.css`
is):
```css
@layer theme, base, components, utilities;
@import 'tailwindcss/theme' layer(theme);
@import 'tailwindcss/preflight' layer(base);
@import 'tailwindcss/utilities' layer(utilities);
/* the rest of your styles imports, styles, etc… */
```
### 2. Split your stylesheet into "main" and "theme" parts
Your "theme" is comprised of the `@import 'tailwindcss/theme'
layer(theme);` import, any custom `@theme` blocks, any `@config`
directives, and any `@plugin` directives. Move all of these into their
own file.
For example, replace this with two files:
```css
@layer theme, base, components, utilities;
@import 'tailwindcss/theme' layer(theme);
@import 'tailwindcss/preflight' layer(base);
@import 'tailwindcss/utilities' layer(utilities);
@theme {
--color-primary: #c0ffee;
}
@plugin "./my-plugin.js";
/* the rest of your styles imports, styles, etc… */
```
with a theme file:
```css
@import 'tailwindcss/theme' layer(theme);
/* all your `@theme` stuff goes in this file */
@theme {
--color-primary: #c0ffee;
}
/* additionally any @config or @plugin does too */
@plugin "./my-plugin.js";
```
and your main CSS file:
```css
@layer theme, base, components, utilities;
@import './my-theme.css'; /* I replaced this import */
@import 'tailwindcss/preflight' layer(base);
@import 'tailwindcss/utilities' layer(utilities);
/* the rest of your styles imports, styles, etc… */
```
### 3. Import only your "theme" file in your Vue components / CSS
modules / etc…
```vue
<style>
@reference "./my-theme.css";
</style>
<template> <!-- ... --> </template>
```
Fixes #17693
This commit is contained in:
parent
d38554d110
commit
6a1df6acf6
3 changed files with 137 additions and 1 deletions
|
|
@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Ensure negative arbitrary `scale` values generate negative values ([#17831](https://github.com/tailwindlabs/tailwindcss/pull/17831))
|
||||
- Fix HAML extraction with embedded Ruby ([#17846](https://github.com/tailwindlabs/tailwindcss/pull/17846))
|
||||
- Don't scan files for utilities when using `@reference` ([#17836](https://github.com/tailwindlabs/tailwindcss/pull/17836))
|
||||
|
||||
## [4.1.5] - 2025-04-30
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, it, test } from 'vitest'
|
||||
import { compile, Polyfills } from '.'
|
||||
import { compile, Features, Polyfills } from '.'
|
||||
import type { PluginAPI } from './compat/plugin-api'
|
||||
import plugin from './plugin'
|
||||
import { compileCss, optimizeCss, run } from './test-utils/run'
|
||||
|
|
@ -5373,3 +5373,130 @@ describe('`@property` polyfill', async () => {
|
|||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('feature detection', () => {
|
||||
test('using `@tailwind utilities`', async () => {
|
||||
let compiler = await compile(css`
|
||||
@tailwind utilities;
|
||||
`)
|
||||
|
||||
expect(compiler.features & Features.Utilities).toBeTruthy()
|
||||
})
|
||||
|
||||
test('using `@apply`', async () => {
|
||||
let compiler = await compile(css`
|
||||
.foo {
|
||||
@apply underline;
|
||||
}
|
||||
`)
|
||||
|
||||
expect(compiler.features & Features.AtApply).toBeTruthy()
|
||||
})
|
||||
|
||||
test('using `@import`', async () => {
|
||||
let compiler = await compile(
|
||||
css`
|
||||
@import 'tailwindcss/preflight';
|
||||
`,
|
||||
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
|
||||
)
|
||||
|
||||
expect(compiler.features & Features.AtImport).toBeTruthy()
|
||||
})
|
||||
|
||||
test('using `@reference`', async () => {
|
||||
let compiler = await compile(
|
||||
css`
|
||||
@import 'tailwindcss/preflight';
|
||||
`,
|
||||
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
|
||||
)
|
||||
|
||||
// There's little difference between `@reference` and `@import` on a feature
|
||||
// level as it's just like an import but with side-effect behavior.
|
||||
//
|
||||
// It's really just shorthand for `@import "…" reference;`
|
||||
expect(compiler.features & Features.AtImport).toBeTruthy()
|
||||
})
|
||||
|
||||
test('using `theme(…)`', async () => {
|
||||
let compiler = await compile(
|
||||
css`
|
||||
@theme {
|
||||
--color-red: #f00;
|
||||
}
|
||||
|
||||
.foo {
|
||||
color: theme(--color-red);
|
||||
}
|
||||
`,
|
||||
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
|
||||
)
|
||||
|
||||
expect(compiler.features & Features.ThemeFunction).toBeTruthy()
|
||||
})
|
||||
|
||||
test('using `@plugin`', async () => {
|
||||
let compiler = await compile(
|
||||
css`
|
||||
@plugin "./some-plugin.js";
|
||||
`,
|
||||
{ loadModule: async (_, base) => ({ base, module: () => {} }) },
|
||||
)
|
||||
|
||||
expect(compiler.features & Features.JsPluginCompat).toBeTruthy()
|
||||
})
|
||||
|
||||
test('using `@config`', async () => {
|
||||
let compiler = await compile(
|
||||
css`
|
||||
@config "./some-config.js";
|
||||
`,
|
||||
{ loadModule: async (_, base) => ({ base, module: {} }) },
|
||||
)
|
||||
|
||||
expect(compiler.features & Features.JsPluginCompat).toBeTruthy()
|
||||
})
|
||||
|
||||
test('using `@variant`', async () => {
|
||||
let compiler = await compile(css`
|
||||
.foo {
|
||||
@variant dark {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
expect(compiler.features & Features.Variants).toBeTruthy()
|
||||
})
|
||||
|
||||
test('legacy `@variant` syntax does not trigger the variant feature', async () => {
|
||||
let compiler = await compile(css`
|
||||
@variant dark (&:is(.dark, .dark *));
|
||||
`)
|
||||
|
||||
expect(compiler.features & Features.Variants).toBeFalsy()
|
||||
})
|
||||
|
||||
test('`@tailwind utilities` is ignored inside `@reference`', async () => {
|
||||
let compiler = await compile(
|
||||
css`
|
||||
@reference "tailwindcss/utilities";
|
||||
`,
|
||||
{
|
||||
async loadStylesheet(id, base) {
|
||||
return {
|
||||
base,
|
||||
content: css`
|
||||
@tailwind utilities;
|
||||
`,
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// We see @tailwind utilities but because of @reference it is ignored
|
||||
expect(compiler.features & Features.AtImport).toBeTruthy()
|
||||
expect(compiler.features & Features.Utilities).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -162,6 +162,14 @@ async function parseCss(
|
|||
return
|
||||
}
|
||||
|
||||
// When inside `@reference` we should treat `@tailwind utilities` as if
|
||||
// it wasn't there in the first place. This should also let `build()`
|
||||
// return the cached static AST.
|
||||
if (context.reference) {
|
||||
replaceWith([])
|
||||
return
|
||||
}
|
||||
|
||||
let params = segment(node.params, ' ')
|
||||
for (let param of params) {
|
||||
if (param.startsWith('source(')) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue