From a8ed0ba667b4018e2c20bde02d10123dcae77b19 Mon Sep 17 00:00:00 2001 From: Navith Date: Thu, 29 Oct 2020 13:20:03 -0400 Subject: [PATCH 1/2] Add failing test for purge preserving element selectors when `defaultExtractor` is overridden --- __tests__/purgeUnusedStyles.test.js | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/__tests__/purgeUnusedStyles.test.js b/__tests__/purgeUnusedStyles.test.js index 5b532217fb6c..839fd3b5e350 100644 --- a/__tests__/purgeUnusedStyles.test.js +++ b/__tests__/purgeUnusedStyles.test.js @@ -658,6 +658,60 @@ test('element selectors are preserved by default', () => { ) }) +test('element selectors are preserved even when defaultExtractor is overridden', () => { + return inProduction( + suppressConsoleLogs(() => { + const inputPath = path.resolve(`${__dirname}/fixtures/tailwind-input.css`) + const input = fs.readFileSync(inputPath, 'utf8') + + return postcss([ + tailwind({ + ...config, + purge: { + content: [path.resolve(`${__dirname}/fixtures/**/*.html`)], + mode: 'all', + preserveHtmlElements: true, + options: { + defaultExtractor: (_content) => [], + }, + }, + }), + ]) + .process(input, { from: inputPath }) + .then((result) => { + const rules = extractRules(result.root) + ;[ + 'a', + 'blockquote', + 'body', + 'code', + 'fieldset', + 'figure', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'hr', + 'html', + 'img', + 'kbd', + 'ol', + 'p', + 'pre', + 'strong', + 'sup', + 'table', + 'ul', + ].forEach((e) => expect(rules).toContain(e)) + + assertPurged(result) + }) + }) + ) +}) + test('preserving element selectors can be disabled', () => { return inProduction( suppressConsoleLogs(() => { From 4de67ce982d324e1c619026400d24c17af101f4d Mon Sep 17 00:00:00 2001 From: Navith Date: Thu, 29 Oct 2020 13:46:02 -0400 Subject: [PATCH 2/2] `preserveHtmlElements` works with user-defined purge extractors --- __tests__/purgeUnusedStyles.test.js | 3 ++- src/lib/purgeUnusedStyles.js | 31 ++++++++++++++++++----------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/__tests__/purgeUnusedStyles.test.js b/__tests__/purgeUnusedStyles.test.js index 839fd3b5e350..3fbc1453460f 100644 --- a/__tests__/purgeUnusedStyles.test.js +++ b/__tests__/purgeUnusedStyles.test.js @@ -2,6 +2,7 @@ import fs from 'fs' import path from 'path' import postcss from 'postcss' import tailwind from '../src/index' +import { tailwindExtractor } from '../src/lib/purgeUnusedStyles' import defaultConfig from '../stubs/defaultConfig.stub.js' function suppressConsoleLogs(cb, type = 'warn') { @@ -672,7 +673,7 @@ test('element selectors are preserved even when defaultExtractor is overridden', mode: 'all', preserveHtmlElements: true, options: { - defaultExtractor: (_content) => [], + defaultExtractor: tailwindExtractor, }, }, }), diff --git a/src/lib/purgeUnusedStyles.js b/src/lib/purgeUnusedStyles.js index 92d075c46682..e75da457d564 100644 --- a/src/lib/purgeUnusedStyles.js +++ b/src/lib/purgeUnusedStyles.js @@ -22,6 +22,17 @@ function removeTailwindMarkers(css) { }) } +export function tailwindExtractor(content) { + // Capture as liberally as possible, including things like `h-(screen-1.5)` + const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [] + const broadMatchesWithoutTrailingSlash = broadMatches.map((match) => _.trimEnd(match, '\\')) + + // Capture classes within other delimiters like .block(class="w-1/2") in Pug + const innerMatches = content.match(/[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g) || [] + + return broadMatches.concat(broadMatchesWithoutTrailingSlash).concat(innerMatches) +} + export default function purgeUnusedUtilities(config, configChanged) { const purgeEnabled = _.get( config, @@ -46,6 +57,8 @@ export default function purgeUnusedUtilities(config, configChanged) { return removeTailwindMarkers } + const { defaultExtractor, ...purgeOptions } = config.purge.options || {} + return postcss([ function (css) { const mode = _.get(config, 'purge.mode', 'layers') @@ -93,22 +106,16 @@ export default function purgeUnusedUtilities(config, configChanged) { purgecss({ content: Array.isArray(config.purge) ? config.purge : config.purge.content, defaultExtractor: (content) => { - // Capture as liberally as possible, including things like `h-(screen-1.5)` - const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [] - const broadMatchesWithoutTrailingSlash = broadMatches.map((match) => _.trimEnd(match, '\\')) - - // Capture classes within other delimiters like .block(class="w-1/2") in Pug - const innerMatches = content.match(/[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g) || [] - - const matches = broadMatches.concat(broadMatchesWithoutTrailingSlash).concat(innerMatches) + const extractor = defaultExtractor || tailwindExtractor + const preserved = [...extractor(content)] if (_.get(config, 'purge.preserveHtmlElements', true)) { - return [...htmlTags].concat(matches) - } else { - return matches + preserved.push(...htmlTags) } + + return preserved }, - ...config.purge.options, + ...purgeOptions, }), ]) }