From 6a50e6e1605cd8c4e071970211ae6254cc1c6313 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 30 Sep 2024 15:12:03 -0400 Subject: [PATCH] Add `blocklist` support from v3 config files (#14556) This PR adds support for the `blocklist` config option when using a JS config file in v4. You can now block certain classes from being generated at all. This is useful in cases where scanning files sees things that look like classes but are actually not used. For example, in paragraphs in a markdown file: ```js // tailwind.config.js export default { blocklist: ['bg-red-500'], } ``` ```html
``` Output: ```css .text-black/75 { color: rgba(0, 0, 0, 0.75); } ``` --- CHANGELOG.md | 1 + .../src/compat/apply-compat-hooks.ts | 4 ++ .../tailwindcss/src/compat/config.test.ts | 69 +++++++++++++++++++ .../src/compat/config/resolve-config.ts | 5 ++ .../tailwindcss/src/compat/config/types.ts | 9 +++ packages/tailwindcss/src/compile.ts | 5 ++ packages/tailwindcss/src/design-system.ts | 15 +++- packages/tailwindcss/src/index.ts | 5 +- packages/tailwindcss/src/intellisense.test.ts | 21 ++++++ 9 files changed, 129 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e40421238..2b7eab50d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add support for prefixes ([#14501](https://github.com/tailwindlabs/tailwindcss/pull/14501)) - Expose timing information in debug mode ([#14553](https://github.com/tailwindlabs/tailwindcss/pull/14553)) +- Add support for `blocklist` in config files ([#14556](https://github.com/tailwindlabs/tailwindcss/pull/14556)) - _Experimental_: Add template codemods for migrating `bg-gradient-*` utilities to `bg-linear-*` ([#14537](https://github.com/tailwindlabs/tailwindcss/pull/14537])) - _Experimental_: Migrate `@import "tailwindcss/tailwind.css"` to `@import "tailwindcss"` ([#14514](https://github.com/tailwindlabs/tailwindcss/pull/14514)) diff --git a/packages/tailwindcss/src/compat/apply-compat-hooks.ts b/packages/tailwindcss/src/compat/apply-compat-hooks.ts index 1cf3cc810..915cfea42 100644 --- a/packages/tailwindcss/src/compat/apply-compat-hooks.ts +++ b/packages/tailwindcss/src/compat/apply-compat-hooks.ts @@ -229,6 +229,10 @@ export async function applyCompatibilityHooks({ designSystem.theme.prefix = resolvedConfig.prefix } + for (let candidate of resolvedConfig.blocklist) { + designSystem.invalidCandidates.add(candidate) + } + // Replace `resolveThemeValue` with a version that is backwards compatible // with dot-notation but also aware of any JS theme configurations registered // by plugins or JS config files. This is significantly slower than just diff --git a/packages/tailwindcss/src/compat/config.test.ts b/packages/tailwindcss/src/compat/config.test.ts index 4725a8f2d..bb25a0231 100644 --- a/packages/tailwindcss/src/compat/config.test.ts +++ b/packages/tailwindcss/src/compat/config.test.ts @@ -1371,3 +1371,72 @@ test('a prefix must be letters only', async () => { `[Error: The prefix "__" is invalid. Prefixes must be lowercase ASCII letters (a-z) only.]`, ) }) + +test('blocklisted canddiates are not generated', async () => { + let compiler = await compile( + css` + @theme reference { + --color-white: #fff; + --breakpoint-md: 48rem; + } + @tailwind utilities; + @config "./config.js"; + `, + { + async loadModule(id, base) { + return { + base, + module: { + blocklist: ['bg-white'], + }, + } + }, + }, + ) + + // bg-white will not get generated + expect(compiler.build(['bg-white'])).toEqual('') + + // underline will as will md:bg-white + expect(compiler.build(['underline', 'bg-white', 'md:bg-white'])).toMatchInlineSnapshot(` + ".underline { + text-decoration-line: underline; + } + .md\\:bg-white { + @media (width >= 48rem) { + background-color: var(--color-white, #fff); + } + } + " + `) +}) + +test('blocklisted canddiates cannot be used with `@apply`', async () => { + await expect(() => + compile( + css` + @theme reference { + --color-white: #fff; + --breakpoint-md: 48rem; + } + @tailwind utilities; + @config "./config.js"; + .foo { + @apply bg-white; + } + `, + { + async loadModule(id, base) { + return { + base, + module: { + blocklist: ['bg-white'], + }, + } + }, + }, + ), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Cannot apply unknown utility class: bg-white]`, + ) +}) diff --git a/packages/tailwindcss/src/compat/config/resolve-config.ts b/packages/tailwindcss/src/compat/config/resolve-config.ts index 0c20bf7c3..acf89c3cf 100644 --- a/packages/tailwindcss/src/compat/config/resolve-config.ts +++ b/packages/tailwindcss/src/compat/config/resolve-config.ts @@ -27,6 +27,7 @@ interface ResolutionContext { } let minimal: ResolvedConfig = { + blocklist: [], prefix: '', darkMode: null, theme: {}, @@ -64,6 +65,10 @@ export function resolveConfig(design: DesignSystem, files: ConfigFile[]): Resolv if ('prefix' in config && config.prefix !== undefined) { ctx.result.prefix = config.prefix ?? '' } + + if ('blocklist' in config && config.blocklist !== undefined) { + ctx.result.blocklist = config.blocklist ?? [] + } } // Merge themes diff --git a/packages/tailwindcss/src/compat/config/types.ts b/packages/tailwindcss/src/compat/config/types.ts index 4b6e072b5..289e1bae0 100644 --- a/packages/tailwindcss/src/compat/config/types.ts +++ b/packages/tailwindcss/src/compat/config/types.ts @@ -78,3 +78,12 @@ export interface UserConfig { export interface ResolvedConfig { prefix: string } + +// `blocklist` support +export interface UserConfig { + blocklist?: string[] +} + +export interface ResolvedConfig { + blocklist: string[] +} diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index 9b7b3ce46..c5de56c4c 100644 --- a/packages/tailwindcss/src/compile.ts +++ b/packages/tailwindcss/src/compile.ts @@ -22,6 +22,11 @@ export function compileCandidates( // Parse candidates and variants for (let rawCandidate of rawCandidates) { + if (designSystem.invalidCandidates.has(rawCandidate)) { + onInvalidCandidate?.(rawCandidate) + continue // Bail, invalid candidate + } + let candidates = designSystem.parseCandidate(rawCandidate) if (candidates.length === 0) { onInvalidCandidate?.(rawCandidate) diff --git a/packages/tailwindcss/src/design-system.ts b/packages/tailwindcss/src/design-system.ts index 0bac8102d..e2b07a24b 100644 --- a/packages/tailwindcss/src/design-system.ts +++ b/packages/tailwindcss/src/design-system.ts @@ -13,6 +13,8 @@ export type DesignSystem = { utilities: Utilities variants: Variants + invalidCandidates: Set + getClassOrder(classes: string[]): [string, bigint | null][] getClassList(): ClassEntry[] getVariants(): VariantEntry[] @@ -45,12 +47,21 @@ export function buildDesignSystem(theme: Theme): DesignSystem { utilities, variants, + invalidCandidates: new Set(), + candidatesToCss(classes: string[]) { let result: (string | null)[] = [] for (let className of classes) { - let { astNodes } = compileCandidates([className], this) - if (astNodes.length === 0) { + let wasInvalid = false + + let { astNodes } = compileCandidates([className], this, { + onInvalidCandidate(candidate) { + wasInvalid = true + }, + }) + + if (astNodes.length === 0 || wasInvalid) { result.push(null) } else { result.push(toCss(astNodes)) diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index ff74f1881..82c8203a5 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -399,9 +399,8 @@ export async function compile( } // Track all invalid candidates - let invalidCandidates = new Set() function onInvalidCandidate(candidate: string) { - invalidCandidates.add(candidate) + designSystem.invalidCandidates.add(candidate) } // Track all valid candidates, these are the incoming `rawCandidate` that @@ -419,7 +418,7 @@ export async function compile( // Add all new candidates unless we know that they are invalid. let prevSize = allValidCandidates.size for (let candidate of newRawCandidates) { - if (!invalidCandidates.has(candidate)) { + if (!designSystem.invalidCandidates.has(candidate)) { allValidCandidates.add(candidate) didChange ||= allValidCandidates.size !== prevSize } diff --git a/packages/tailwindcss/src/intellisense.test.ts b/packages/tailwindcss/src/intellisense.test.ts index 0eb4d16cc..f6a906c28 100644 --- a/packages/tailwindcss/src/intellisense.test.ts +++ b/packages/tailwindcss/src/intellisense.test.ts @@ -83,3 +83,24 @@ test('The variant `has-force` does not crash', () => { expect(has.selectors({ value: 'force' })).toMatchInlineSnapshot(`[]`) }) + +test('Can produce CSS per candidate using `candidatesToCss`', () => { + let design = loadDesignSystem() + design.invalidCandidates = new Set(['bg-[#fff]']) + + expect(design.candidatesToCss(['underline', 'i-dont-exist', 'bg-[#fff]', 'bg-[#000]'])) + .toMatchInlineSnapshot(` + [ + ".underline { + text-decoration-line: underline; + } + ", + null, + null, + ".bg-\\[\\#000\\] { + background-color: #000; + } + ", + ] + `) +})