diff --git a/CHANGELOG.md b/CHANGELOG.md index cb7712480e0e..83a777060587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Allow users to block generation of certain utilities ([#9812](https://github.com/tailwindlabs/tailwindcss/pull/9812)) + ### Fixed - Fix watching of files on Linux when renames are involved ([#9796](https://github.com/tailwindlabs/tailwindcss/pull/9796)) diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index a5f8d3fdfb67..7b815ffbeaa0 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -1165,7 +1165,8 @@ export function createContext(tailwindConfig, changedContent = [], root = postcs candidateRuleCache: new Map(), classCache: new Map(), applyClassCache: new Map(), - notClassCache: new Set(), + // Seed the not class cache with the blocklist (which is only strings) + notClassCache: new Set(tailwindConfig.blocklist ?? []), postCssNodeCache: new Map(), candidateRuleMap: new Map(), tailwindConfig, diff --git a/src/util/normalizeConfig.js b/src/util/normalizeConfig.js index baee48394d56..95f63a39eeee 100644 --- a/src/util/normalizeConfig.js +++ b/src/util/normalizeConfig.js @@ -150,6 +150,24 @@ export function normalizeConfig(config) { return [] })() + // Normalize the `blocklist` + config.blocklist = (() => { + let { blocklist } = config + + if (Array.isArray(blocklist)) { + if (blocklist.every((item) => typeof item === 'string')) { + return blocklist + } + + log.warn('blocklist-invalid', [ + 'The `blocklist` option must be an array of strings.', + 'https://tailwindcss.com/docs/content-configuration#discarding-classes', + ]) + } + + return [] + })() + // Normalize prefix option if (typeof config.prefix === 'function') { log.warn('prefix-function', [ diff --git a/tests/blocklist.test.js b/tests/blocklist.test.js new file mode 100644 index 000000000000..9fc319a9c908 --- /dev/null +++ b/tests/blocklist.test.js @@ -0,0 +1,116 @@ +import { run, html, css } from './util/run' + +let warn + +beforeEach(() => { + warn = jest.spyOn(require('../src/util/log').default, 'warn') +}) + +afterEach(() => warn.mockClear()) + +it('can block classes matched literally', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50', 'my-custom-class'], + } + + let input = css` + @tailwind utilities; + .my-custom-class { + color: red; + } + ` + + return run(input, config).then((result) => { + return expect(result.css).toMatchCss(css` + .font-bold { + font-weight: 700; + } + .my-custom-class { + color: red; + } + @media (min-width: 640px) { + .sm\:hover\:text-sm:hover { + font-size: 0.875rem; + line-height: 1.25rem; + } + } + `) + }) +}) + +it('can block classes inside @layer', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + blocklist: ['my-custom-class'], + } + + let input = css` + @tailwind utilities; + @layer utilities { + .my-custom-class { + color: red; + } + } + ` + + return run(input, config).then((result) => { + return expect(result.css).toMatchCss(css` + .font-bold { + font-weight: 700; + } + `) + }) +}) + +it('blocklists do NOT support regexes', async () => { + let config = { + content: [{ raw: html`
` }], + blocklist: [/^bg-\[[^]+\]$/], + } + + let result = await run('@tailwind utilities', config) + + expect(result.css).toMatchCss(css` + .bg-\[\#f00d1e\] { + --tw-bg-opacity: 1; + background-color: rgb(240 13 30 / var(--tw-bg-opacity)); + } + .font-bold { + font-weight: 700; + } + `) + + expect(warn).toHaveBeenCalledTimes(1) + expect(warn.mock.calls.map((x) => x[0])).toEqual(['blocklist-invalid']) +}) + +it('can block classes generated by the safelist', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [{ pattern: /^bg-red-(400|500)$/ }], + blocklist: ['bg-red-500'], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchCss(css` + .bg-red-400 { + --tw-bg-opacity: 1; + background-color: rgb(248 113 113 / var(--tw-bg-opacity)); + } + .font-bold { + font-weight: 700; + } + `) + }) +})