Skip to content

Commit 6a50e6e

Browse files
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 <!-- index.html --> <div class="bg-red-500 text-black/75"></div> ``` Output: ```css .text-black/75 { color: rgba(0, 0, 0, 0.75); } ```
1 parent ab82efa commit 6a50e6e

File tree

9 files changed

+129
-5
lines changed

9 files changed

+129
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Add support for prefixes ([#14501](https://github.com/tailwindlabs/tailwindcss/pull/14501))
1313
- Expose timing information in debug mode ([#14553](https://github.com/tailwindlabs/tailwindcss/pull/14553))
14+
- Add support for `blocklist` in config files ([#14556](https://github.com/tailwindlabs/tailwindcss/pull/14556))
1415
- _Experimental_: Add template codemods for migrating `bg-gradient-*` utilities to `bg-linear-*` ([#14537](https://github.com/tailwindlabs/tailwindcss/pull/14537]))
1516
- _Experimental_: Migrate `@import "tailwindcss/tailwind.css"` to `@import "tailwindcss"` ([#14514](https://github.com/tailwindlabs/tailwindcss/pull/14514))
1617

packages/tailwindcss/src/compat/apply-compat-hooks.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ export async function applyCompatibilityHooks({
229229
designSystem.theme.prefix = resolvedConfig.prefix
230230
}
231231

232+
for (let candidate of resolvedConfig.blocklist) {
233+
designSystem.invalidCandidates.add(candidate)
234+
}
235+
232236
// Replace `resolveThemeValue` with a version that is backwards compatible
233237
// with dot-notation but also aware of any JS theme configurations registered
234238
// by plugins or JS config files. This is significantly slower than just

packages/tailwindcss/src/compat/config.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,3 +1371,72 @@ test('a prefix must be letters only', async () => {
13711371
`[Error: The prefix "__" is invalid. Prefixes must be lowercase ASCII letters (a-z) only.]`,
13721372
)
13731373
})
1374+
1375+
test('blocklisted canddiates are not generated', async () => {
1376+
let compiler = await compile(
1377+
css`
1378+
@theme reference {
1379+
--color-white: #fff;
1380+
--breakpoint-md: 48rem;
1381+
}
1382+
@tailwind utilities;
1383+
@config "./config.js";
1384+
`,
1385+
{
1386+
async loadModule(id, base) {
1387+
return {
1388+
base,
1389+
module: {
1390+
blocklist: ['bg-white'],
1391+
},
1392+
}
1393+
},
1394+
},
1395+
)
1396+
1397+
// bg-white will not get generated
1398+
expect(compiler.build(['bg-white'])).toEqual('')
1399+
1400+
// underline will as will md:bg-white
1401+
expect(compiler.build(['underline', 'bg-white', 'md:bg-white'])).toMatchInlineSnapshot(`
1402+
".underline {
1403+
text-decoration-line: underline;
1404+
}
1405+
.md\\:bg-white {
1406+
@media (width >= 48rem) {
1407+
background-color: var(--color-white, #fff);
1408+
}
1409+
}
1410+
"
1411+
`)
1412+
})
1413+
1414+
test('blocklisted canddiates cannot be used with `@apply`', async () => {
1415+
await expect(() =>
1416+
compile(
1417+
css`
1418+
@theme reference {
1419+
--color-white: #fff;
1420+
--breakpoint-md: 48rem;
1421+
}
1422+
@tailwind utilities;
1423+
@config "./config.js";
1424+
.foo {
1425+
@apply bg-white;
1426+
}
1427+
`,
1428+
{
1429+
async loadModule(id, base) {
1430+
return {
1431+
base,
1432+
module: {
1433+
blocklist: ['bg-white'],
1434+
},
1435+
}
1436+
},
1437+
},
1438+
),
1439+
).rejects.toThrowErrorMatchingInlineSnapshot(
1440+
`[Error: Cannot apply unknown utility class: bg-white]`,
1441+
)
1442+
})

packages/tailwindcss/src/compat/config/resolve-config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface ResolutionContext {
2727
}
2828

2929
let minimal: ResolvedConfig = {
30+
blocklist: [],
3031
prefix: '',
3132
darkMode: null,
3233
theme: {},
@@ -64,6 +65,10 @@ export function resolveConfig(design: DesignSystem, files: ConfigFile[]): Resolv
6465
if ('prefix' in config && config.prefix !== undefined) {
6566
ctx.result.prefix = config.prefix ?? ''
6667
}
68+
69+
if ('blocklist' in config && config.blocklist !== undefined) {
70+
ctx.result.blocklist = config.blocklist ?? []
71+
}
6772
}
6873

6974
// Merge themes

packages/tailwindcss/src/compat/config/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,12 @@ export interface UserConfig {
7878
export interface ResolvedConfig {
7979
prefix: string
8080
}
81+
82+
// `blocklist` support
83+
export interface UserConfig {
84+
blocklist?: string[]
85+
}
86+
87+
export interface ResolvedConfig {
88+
blocklist: string[]
89+
}

packages/tailwindcss/src/compile.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ export function compileCandidates(
2222

2323
// Parse candidates and variants
2424
for (let rawCandidate of rawCandidates) {
25+
if (designSystem.invalidCandidates.has(rawCandidate)) {
26+
onInvalidCandidate?.(rawCandidate)
27+
continue // Bail, invalid candidate
28+
}
29+
2530
let candidates = designSystem.parseCandidate(rawCandidate)
2631
if (candidates.length === 0) {
2732
onInvalidCandidate?.(rawCandidate)

packages/tailwindcss/src/design-system.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export type DesignSystem = {
1313
utilities: Utilities
1414
variants: Variants
1515

16+
invalidCandidates: Set<string>
17+
1618
getClassOrder(classes: string[]): [string, bigint | null][]
1719
getClassList(): ClassEntry[]
1820
getVariants(): VariantEntry[]
@@ -45,12 +47,21 @@ export function buildDesignSystem(theme: Theme): DesignSystem {
4547
utilities,
4648
variants,
4749

50+
invalidCandidates: new Set(),
51+
4852
candidatesToCss(classes: string[]) {
4953
let result: (string | null)[] = []
5054

5155
for (let className of classes) {
52-
let { astNodes } = compileCandidates([className], this)
53-
if (astNodes.length === 0) {
56+
let wasInvalid = false
57+
58+
let { astNodes } = compileCandidates([className], this, {
59+
onInvalidCandidate(candidate) {
60+
wasInvalid = true
61+
},
62+
})
63+
64+
if (astNodes.length === 0 || wasInvalid) {
5465
result.push(null)
5566
} else {
5667
result.push(toCss(astNodes))

packages/tailwindcss/src/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,9 +399,8 @@ export async function compile(
399399
}
400400

401401
// Track all invalid candidates
402-
let invalidCandidates = new Set<string>()
403402
function onInvalidCandidate(candidate: string) {
404-
invalidCandidates.add(candidate)
403+
designSystem.invalidCandidates.add(candidate)
405404
}
406405

407406
// Track all valid candidates, these are the incoming `rawCandidate` that
@@ -419,7 +418,7 @@ export async function compile(
419418
// Add all new candidates unless we know that they are invalid.
420419
let prevSize = allValidCandidates.size
421420
for (let candidate of newRawCandidates) {
422-
if (!invalidCandidates.has(candidate)) {
421+
if (!designSystem.invalidCandidates.has(candidate)) {
423422
allValidCandidates.add(candidate)
424423
didChange ||= allValidCandidates.size !== prevSize
425424
}

packages/tailwindcss/src/intellisense.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,24 @@ test('The variant `has-force` does not crash', () => {
8383

8484
expect(has.selectors({ value: 'force' })).toMatchInlineSnapshot(`[]`)
8585
})
86+
87+
test('Can produce CSS per candidate using `candidatesToCss`', () => {
88+
let design = loadDesignSystem()
89+
design.invalidCandidates = new Set(['bg-[#fff]'])
90+
91+
expect(design.candidatesToCss(['underline', 'i-dont-exist', 'bg-[#fff]', 'bg-[#000]']))
92+
.toMatchInlineSnapshot(`
93+
[
94+
".underline {
95+
text-decoration-line: underline;
96+
}
97+
",
98+
null,
99+
null,
100+
".bg-\\[\\#000\\] {
101+
background-color: #000;
102+
}
103+
",
104+
]
105+
`)
106+
})

0 commit comments

Comments
 (0)