Skip to content

Commit 602101d

Browse files
Allow users to block generation of certain utilities (#9812)
* Add blocklist tests * Build initial implementation of blocklist * wip * wip * wip * Update changelog
1 parent 4ccc0fa commit 602101d

File tree

4 files changed

+140
-1
lines changed

4 files changed

+140
-1
lines changed

CHANGELOG.md

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

88
## [Unreleased]
99

10+
### Added
11+
12+
- Allow users to block generation of certain utilities ([#9812](https://github.com/tailwindlabs/tailwindcss/pull/9812))
13+
1014
### Fixed
1115

1216
- Fix watching of files on Linux when renames are involved ([#9796](https://github.com/tailwindlabs/tailwindcss/pull/9796))

src/lib/setupContextUtils.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,8 @@ export function createContext(tailwindConfig, changedContent = [], root = postcs
11651165
candidateRuleCache: new Map(),
11661166
classCache: new Map(),
11671167
applyClassCache: new Map(),
1168-
notClassCache: new Set(),
1168+
// Seed the not class cache with the blocklist (which is only strings)
1169+
notClassCache: new Set(tailwindConfig.blocklist ?? []),
11691170
postCssNodeCache: new Map(),
11701171
candidateRuleMap: new Map(),
11711172
tailwindConfig,

src/util/normalizeConfig.js

+18
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,24 @@ export function normalizeConfig(config) {
150150
return []
151151
})()
152152

153+
// Normalize the `blocklist`
154+
config.blocklist = (() => {
155+
let { blocklist } = config
156+
157+
if (Array.isArray(blocklist)) {
158+
if (blocklist.every((item) => typeof item === 'string')) {
159+
return blocklist
160+
}
161+
162+
log.warn('blocklist-invalid', [
163+
'The `blocklist` option must be an array of strings.',
164+
'https://tailwindcss.com/docs/content-configuration#discarding-classes',
165+
])
166+
}
167+
168+
return []
169+
})()
170+
153171
// Normalize prefix option
154172
if (typeof config.prefix === 'function') {
155173
log.warn('prefix-function', [

tests/blocklist.test.js

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { run, html, css } from './util/run'
2+
3+
let warn
4+
5+
beforeEach(() => {
6+
warn = jest.spyOn(require('../src/util/log').default, 'warn')
7+
})
8+
9+
afterEach(() => warn.mockClear())
10+
11+
it('can block classes matched literally', () => {
12+
let config = {
13+
content: [
14+
{
15+
raw: html`<div
16+
class="font-bold uppercase sm:hover:text-sm hover:text-sm bg-red-500/50 my-custom-class"
17+
></div>`,
18+
},
19+
],
20+
blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50', 'my-custom-class'],
21+
}
22+
23+
let input = css`
24+
@tailwind utilities;
25+
.my-custom-class {
26+
color: red;
27+
}
28+
`
29+
30+
return run(input, config).then((result) => {
31+
return expect(result.css).toMatchCss(css`
32+
.font-bold {
33+
font-weight: 700;
34+
}
35+
.my-custom-class {
36+
color: red;
37+
}
38+
@media (min-width: 640px) {
39+
.sm\:hover\:text-sm:hover {
40+
font-size: 0.875rem;
41+
line-height: 1.25rem;
42+
}
43+
}
44+
`)
45+
})
46+
})
47+
48+
it('can block classes inside @layer', () => {
49+
let config = {
50+
content: [
51+
{
52+
raw: html`<div class="font-bold my-custom-class"></div>`,
53+
},
54+
],
55+
blocklist: ['my-custom-class'],
56+
}
57+
58+
let input = css`
59+
@tailwind utilities;
60+
@layer utilities {
61+
.my-custom-class {
62+
color: red;
63+
}
64+
}
65+
`
66+
67+
return run(input, config).then((result) => {
68+
return expect(result.css).toMatchCss(css`
69+
.font-bold {
70+
font-weight: 700;
71+
}
72+
`)
73+
})
74+
})
75+
76+
it('blocklists do NOT support regexes', async () => {
77+
let config = {
78+
content: [{ raw: html`<div class="font-bold bg-[#f00d1e]"></div>` }],
79+
blocklist: [/^bg-\[[^]+\]$/],
80+
}
81+
82+
let result = await run('@tailwind utilities', config)
83+
84+
expect(result.css).toMatchCss(css`
85+
.bg-\[\#f00d1e\] {
86+
--tw-bg-opacity: 1;
87+
background-color: rgb(240 13 30 / var(--tw-bg-opacity));
88+
}
89+
.font-bold {
90+
font-weight: 700;
91+
}
92+
`)
93+
94+
expect(warn).toHaveBeenCalledTimes(1)
95+
expect(warn.mock.calls.map((x) => x[0])).toEqual(['blocklist-invalid'])
96+
})
97+
98+
it('can block classes generated by the safelist', () => {
99+
let config = {
100+
content: [{ raw: html`<div class="font-bold"></div>` }],
101+
safelist: [{ pattern: /^bg-red-(400|500)$/ }],
102+
blocklist: ['bg-red-500'],
103+
}
104+
105+
return run('@tailwind utilities', config).then((result) => {
106+
return expect(result.css).toMatchCss(css`
107+
.bg-red-400 {
108+
--tw-bg-opacity: 1;
109+
background-color: rgb(248 113 113 / var(--tw-bg-opacity));
110+
}
111+
.font-bold {
112+
font-weight: 700;
113+
}
114+
`)
115+
})
116+
})

0 commit comments

Comments
 (0)