Skip to content

Commit 6a1df6a

Browse files
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
1 parent d38554d commit 6a1df6a

File tree

3 files changed

+137
-1
lines changed

3 files changed

+137
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
- Ensure negative arbitrary `scale` values generate negative values ([#17831](https://github.com/tailwindlabs/tailwindcss/pull/17831))
1717
- Fix HAML extraction with embedded Ruby ([#17846](https://github.com/tailwindlabs/tailwindcss/pull/17846))
18+
- Don't scan files for utilities when using `@reference` ([#17836](https://github.com/tailwindlabs/tailwindcss/pull/17836))
1819

1920
## [4.1.5] - 2025-04-30
2021

packages/tailwindcss/src/index.test.ts

+128-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from 'node:fs'
22
import path from 'node:path'
33
import { describe, expect, it, test } from 'vitest'
4-
import { compile, Polyfills } from '.'
4+
import { compile, Features, Polyfills } from '.'
55
import type { PluginAPI } from './compat/plugin-api'
66
import plugin from './plugin'
77
import { compileCss, optimizeCss, run } from './test-utils/run'
@@ -5373,3 +5373,130 @@ describe('`@property` polyfill', async () => {
53735373
`)
53745374
})
53755375
})
5376+
5377+
describe('feature detection', () => {
5378+
test('using `@tailwind utilities`', async () => {
5379+
let compiler = await compile(css`
5380+
@tailwind utilities;
5381+
`)
5382+
5383+
expect(compiler.features & Features.Utilities).toBeTruthy()
5384+
})
5385+
5386+
test('using `@apply`', async () => {
5387+
let compiler = await compile(css`
5388+
.foo {
5389+
@apply underline;
5390+
}
5391+
`)
5392+
5393+
expect(compiler.features & Features.AtApply).toBeTruthy()
5394+
})
5395+
5396+
test('using `@import`', async () => {
5397+
let compiler = await compile(
5398+
css`
5399+
@import 'tailwindcss/preflight';
5400+
`,
5401+
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
5402+
)
5403+
5404+
expect(compiler.features & Features.AtImport).toBeTruthy()
5405+
})
5406+
5407+
test('using `@reference`', async () => {
5408+
let compiler = await compile(
5409+
css`
5410+
@import 'tailwindcss/preflight';
5411+
`,
5412+
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
5413+
)
5414+
5415+
// There's little difference between `@reference` and `@import` on a feature
5416+
// level as it's just like an import but with side-effect behavior.
5417+
//
5418+
// It's really just shorthand for `@import "…" reference;`
5419+
expect(compiler.features & Features.AtImport).toBeTruthy()
5420+
})
5421+
5422+
test('using `theme(…)`', async () => {
5423+
let compiler = await compile(
5424+
css`
5425+
@theme {
5426+
--color-red: #f00;
5427+
}
5428+
5429+
.foo {
5430+
color: theme(--color-red);
5431+
}
5432+
`,
5433+
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
5434+
)
5435+
5436+
expect(compiler.features & Features.ThemeFunction).toBeTruthy()
5437+
})
5438+
5439+
test('using `@plugin`', async () => {
5440+
let compiler = await compile(
5441+
css`
5442+
@plugin "./some-plugin.js";
5443+
`,
5444+
{ loadModule: async (_, base) => ({ base, module: () => {} }) },
5445+
)
5446+
5447+
expect(compiler.features & Features.JsPluginCompat).toBeTruthy()
5448+
})
5449+
5450+
test('using `@config`', async () => {
5451+
let compiler = await compile(
5452+
css`
5453+
@config "./some-config.js";
5454+
`,
5455+
{ loadModule: async (_, base) => ({ base, module: {} }) },
5456+
)
5457+
5458+
expect(compiler.features & Features.JsPluginCompat).toBeTruthy()
5459+
})
5460+
5461+
test('using `@variant`', async () => {
5462+
let compiler = await compile(css`
5463+
.foo {
5464+
@variant dark {
5465+
color: red;
5466+
}
5467+
}
5468+
`)
5469+
5470+
expect(compiler.features & Features.Variants).toBeTruthy()
5471+
})
5472+
5473+
test('legacy `@variant` syntax does not trigger the variant feature', async () => {
5474+
let compiler = await compile(css`
5475+
@variant dark (&:is(.dark, .dark *));
5476+
`)
5477+
5478+
expect(compiler.features & Features.Variants).toBeFalsy()
5479+
})
5480+
5481+
test('`@tailwind utilities` is ignored inside `@reference`', async () => {
5482+
let compiler = await compile(
5483+
css`
5484+
@reference "tailwindcss/utilities";
5485+
`,
5486+
{
5487+
async loadStylesheet(id, base) {
5488+
return {
5489+
base,
5490+
content: css`
5491+
@tailwind utilities;
5492+
`,
5493+
}
5494+
},
5495+
},
5496+
)
5497+
5498+
// We see @tailwind utilities but because of @reference it is ignored
5499+
expect(compiler.features & Features.AtImport).toBeTruthy()
5500+
expect(compiler.features & Features.Utilities).toBeFalsy()
5501+
})
5502+
})

packages/tailwindcss/src/index.ts

+8
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,14 @@ async function parseCss(
162162
return
163163
}
164164

165+
// When inside `@reference` we should treat `@tailwind utilities` as if
166+
// it wasn't there in the first place. This should also let `build()`
167+
// return the cached static AST.
168+
if (context.reference) {
169+
replaceWith([])
170+
return
171+
}
172+
165173
let params = segment(node.params, ' ')
166174
for (let param of params) {
167175
if (param.startsWith('source(')) {

0 commit comments

Comments
 (0)