diff --git a/CHANGELOG.md b/CHANGELOG.md index 16cf63f53d83..ef34a1a71862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 88ea541a13b0..00e73a90b405 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -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() + }) +}) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 8d953782f621..e8f34e366327 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -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(')) {