diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index e35edcc14145..77a0f77c90ba 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -638,15 +638,6 @@ function registerPlugins(plugins, context) { continue } - if (value instanceof RegExp) { - log.warn('root-regex', [ - 'Regular expressions in `safelist` work differently in Tailwind CSS v3.0.', - 'Update your `safelist` configuration to eliminate this warning.', - 'https://tailwindcss.com/docs/content-configuration#safelisting-classes', - ]) - continue - } - checks.push(value) } @@ -683,7 +674,42 @@ function registerPlugins(plugins, context) { : [util] for (let util of utils) { - for (let { pattern, variants = [] } of checks) { + for (let check of checks) { + let [pattern = /(?:)/, variants = [], opacities = []] = (() => { + if (check instanceof RegExp) { + return [check] + } + + if (Array.isArray(check)) { + if (check.length == 1) { + return check + } + + if (check.length == 2) { + if (check[1] instanceof RegExp) { + let [variants, pattern] = check + return [pattern, variants] + } + + let [pattern, opacities] = check + return [pattern, [], opacities] + } + + if (check.length == 3) { + let [variants, pattern, opacities] = check + return [pattern, variants, opacities] + } + + throw new Error( + `Array values in your Tailwind CSS \`safelist\` must contain 1–3 items, found invalid entry containing ${check.length}.` + ) + } + + // Check must be an object in the shape of { pattern: ..., variants: ... } + let { pattern, variants = [] } = check + return [pattern, variants] + })() + // RegExp with the /g flag are stateful, so let's reset the last // index pointer to reset the state. pattern.lastIndex = 0 @@ -702,6 +728,20 @@ function registerPlugins(plugins, context) { content: variant + context.tailwindConfig.separator + util, extension: 'html', }) + + for (let opacity of opacities) { + context.changedContent.push({ + content: variant + context.tailwindConfig.separator + util + '/' + opacity, + extension: 'html', + }) + } + } + + for (let opacity of opacities) { + context.changedContent.push({ + content: util + '/' + opacity, + extension: 'html', + }) } } } diff --git a/tests/safelist.test.js b/tests/safelist.test.js index d81a6b796ae4..6eb162031f52 100644 --- a/tests/safelist.test.js +++ b/tests/safelist.test.js @@ -86,6 +86,135 @@ it('should safelist based on a pattern regex', () => { }) }) +it('should safelist based on a regex', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [/bg-(red)-(100|200)/], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchCss(css` + .bg-red-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); + } + + .bg-red-200 { + --tw-bg-opacity: 1; + background-color: rgb(254 202 202 / var(--tw-bg-opacity)); + } + + .uppercase { + text-transform: uppercase; + } + `) + }) +}) + +it('should safelist based on a tuple with regex', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [ + [/bg-(red)-(100|200)/], + [['hover'], /bg-(blue)-(100|200)/], + [/bg-(green)-(100|200)/, ['50']], + [['hover'], /bg-(yellow)-(100|200)/, ['50']], + ], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchCss(css` + .bg-red-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); + } + + .bg-red-200 { + --tw-bg-opacity: 1; + background-color: rgb(254 202 202 / var(--tw-bg-opacity)); + } + + .bg-yellow-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 249 195 / var(--tw-bg-opacity)); + } + + .bg-yellow-100\/50 { + background-color: rgb(254 249 195 / 0.5); + } + + .bg-yellow-200 { + --tw-bg-opacity: 1; + background-color: rgb(254 240 138 / var(--tw-bg-opacity)); + } + + .bg-yellow-200\/50 { + background-color: rgb(254 240 138 / 0.5); + } + + .bg-green-100 { + --tw-bg-opacity: 1; + background-color: rgb(220 252 231 / var(--tw-bg-opacity)); + } + + .bg-green-100\/50 { + background-color: rgb(220 252 231 / 0.5); + } + + .bg-green-200 { + --tw-bg-opacity: 1; + background-color: rgb(187 247 208 / var(--tw-bg-opacity)); + } + + .bg-green-200\/50 { + background-color: rgb(187 247 208 / 0.5); + } + + .bg-blue-100 { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity)); + } + + .bg-blue-200 { + --tw-bg-opacity: 1; + background-color: rgb(191 219 254 / var(--tw-bg-opacity)); + } + + .uppercase { + text-transform: uppercase; + } + + .hover\:bg-yellow-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(254 249 195 / var(--tw-bg-opacity)); + } + + .hover\:bg-yellow-100\/50:hover { + background-color: rgb(254 249 195 / 0.5); + } + + .hover\:bg-yellow-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(254 240 138 / var(--tw-bg-opacity)); + } + + .hover\:bg-yellow-200\/50:hover { + background-color: rgb(254 240 138 / 0.5); + } + + .hover\:bg-blue-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity)); + } + + .hover\:bg-blue-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(191 219 254 / var(--tw-bg-opacity)); + } + `) + }) +}) + it('should not generate duplicates', () => { let config = { content: [{ raw: html`
` }],