From 0a5c48c7ad06f45ea618b68edd5a593fd9cb62c6 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 11 Nov 2022 10:59:44 -0500 Subject: [PATCH 1/6] Add blocklist tests --- tests/blocklist.test.js | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/blocklist.test.js diff --git a/tests/blocklist.test.js b/tests/blocklist.test.js new file mode 100644 index 000000000000..13171c7063e7 --- /dev/null +++ b/tests/blocklist.test.js @@ -0,0 +1,51 @@ +import { run, html, css } from './util/run' + +it('can block classes matched literally', () => { + let config = { + content: [{ raw: html`
` }], + blocklist: ['uppercase'], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchCss(css` + .font-bold { + font-weight: 700; + } + `) + }) +}) + +it('can block classes matching a pattern', () => { + let config = { + content: [{ raw: html`
` }], + blocklist: [/^bg-\[[^]+\]$/], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchCss(css` + .font-bold { + font-weight: 700; + } + `) + }) +}) + +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; + } + `) + }) +}) From 9ceb34690d06eeffd2a800bc4b96c5a6a3c01c21 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 11 Nov 2022 11:07:27 -0500 Subject: [PATCH 2/6] Build initial implementation of blocklist --- src/lib/generateRules.js | 23 +++++++++++++++++++++++ src/util/normalizeConfig.js | 9 +++++++++ 2 files changed, 32 insertions(+) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 339440d00812..61d009b52945 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -793,6 +793,24 @@ function getImportantStrategy(important) { } } +/** + * + * @param {string} candidate + * @param {*} context + * @returns {boolean} + */ +function isInBlocklist(candidate, context) { + let blocklist = context.tailwindConfig.blocklist ?? [] + + return blocklist.some((blocked) => { + if (typeof blocked === 'string') { + return blocked === candidate + } + + return blocked.test(candidate) + }) +} + function generateRules(candidates, context) { let allRules = [] let strategy = getImportantStrategy(context.tailwindConfig.important) @@ -807,6 +825,11 @@ function generateRules(candidates, context) { continue } + if (isInBlocklist(candidate, context)) { + context.notClassCache.add(candidate) + continue + } + let matches = Array.from(resolveMatches(candidate, context)) if (matches.length === 0) { diff --git a/src/util/normalizeConfig.js b/src/util/normalizeConfig.js index baee48394d56..6b92dcdc2b9f 100644 --- a/src/util/normalizeConfig.js +++ b/src/util/normalizeConfig.js @@ -150,6 +150,15 @@ export function normalizeConfig(config) { return [] })() + // Normalize the `blocklist` + config.blocklist = (() => { + let { blocklist } = config + + if (Array.isArray(blocklist)) return blocklist + + return [] + })() + // Normalize prefix option if (typeof config.prefix === 'function') { log.warn('prefix-function', [ From 1064632b12b3a46b80ba94f95ea22dd017b139d7 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 11 Nov 2022 11:22:45 -0500 Subject: [PATCH 3/6] wip --- src/lib/generateRules.js | 23 ----------------------- src/lib/setupContextUtils.js | 3 ++- src/util/normalizeConfig.js | 11 ++++++++++- tests/blocklist.test.js | 35 +++++++++++++++++++++++++---------- 4 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 61d009b52945..339440d00812 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -793,24 +793,6 @@ function getImportantStrategy(important) { } } -/** - * - * @param {string} candidate - * @param {*} context - * @returns {boolean} - */ -function isInBlocklist(candidate, context) { - let blocklist = context.tailwindConfig.blocklist ?? [] - - return blocklist.some((blocked) => { - if (typeof blocked === 'string') { - return blocked === candidate - } - - return blocked.test(candidate) - }) -} - function generateRules(candidates, context) { let allRules = [] let strategy = getImportantStrategy(context.tailwindConfig.important) @@ -825,11 +807,6 @@ function generateRules(candidates, context) { continue } - if (isInBlocklist(candidate, context)) { - context.notClassCache.add(candidate) - continue - } - let matches = Array.from(resolveMatches(candidate, context)) if (matches.length === 0) { 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 6b92dcdc2b9f..95f63a39eeee 100644 --- a/src/util/normalizeConfig.js +++ b/src/util/normalizeConfig.js @@ -154,7 +154,16 @@ export function normalizeConfig(config) { config.blocklist = (() => { let { blocklist } = config - if (Array.isArray(blocklist)) return blocklist + 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 [] })() diff --git a/tests/blocklist.test.js b/tests/blocklist.test.js index 13171c7063e7..e6eba82c68f2 100644 --- a/tests/blocklist.test.js +++ b/tests/blocklist.test.js @@ -1,9 +1,17 @@ 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: ['uppercase'], + content: [{ raw: html`
` }], + blocklist: ['uppercase', 'hover:text-sm', 'bg-red-500/50'], } return run('@tailwind utilities', config).then((result) => { @@ -15,19 +23,26 @@ it('can block classes matched literally', () => { }) }) -it('can block classes matching a pattern', () => { +it('blocklists do NOT support regexes', async () => { let config = { content: [{ raw: html`
` }], blocklist: [/^bg-\[[^]+\]$/], } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css` - .font-bold { - font-weight: 700; - } - `) - }) + 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', () => { From 0036649284086abe2de9b2bc2badde15811e549b Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 11 Nov 2022 11:26:13 -0500 Subject: [PATCH 4/6] wip --- tests/blocklist.test.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/blocklist.test.js b/tests/blocklist.test.js index e6eba82c68f2..53615b3ab6af 100644 --- a/tests/blocklist.test.js +++ b/tests/blocklist.test.js @@ -10,8 +10,14 @@ afterEach(() => warn.mockClear()) it('can block classes matched literally', () => { let config = { - content: [{ raw: html`
` }], - blocklist: ['uppercase', 'hover:text-sm', 'bg-red-500/50'], + content: [ + { + raw: html`
`, + }, + ], + blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50'], } return run('@tailwind utilities', config).then((result) => { @@ -19,6 +25,12 @@ it('can block classes matched literally', () => { .font-bold { font-weight: 700; } + @media (min-width: 640px) { + .sm\:hover\:text-sm:hover { + font-size: 0.875rem; + line-height: 1.25rem; + } + } `) }) }) From 988617fb474b74d56bbd62fe12e1bd405afdd06f Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 11 Nov 2022 11:30:42 -0500 Subject: [PATCH 5/6] wip --- tests/blocklist.test.js | 44 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/tests/blocklist.test.js b/tests/blocklist.test.js index 53615b3ab6af..9fc319a9c908 100644 --- a/tests/blocklist.test.js +++ b/tests/blocklist.test.js @@ -13,18 +13,28 @@ it('can block classes matched literally', () => { content: [ { raw: html`
`, }, ], - blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50'], + blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50', 'my-custom-class'], } - return run('@tailwind utilities', config).then((result) => { + 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; @@ -35,6 +45,34 @@ it('can block classes matched literally', () => { }) }) +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`
` }], From 6b8a8983eb1400dafdabf47c37df9066d7cd48b7 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 11 Nov 2022 11:43:54 -0500 Subject: [PATCH 6/6] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) 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))