From 053d6d7f68833225d30e07516f414f8d83bf0695 Mon Sep 17 00:00:00 2001 From: Sergey Tatarintsev Date: Fri, 11 Jun 2021 19:02:52 +0200 Subject: [PATCH 1/2] Improve `purge` performance in layers mode In layers mode, skip `purgecss` completely if source stylesheet does not have any tailwind layers. For the legacy codebases with a lot of non-tailwind stylesheets, it dratically improves the performance of the production build. --- package-lock.json | 21 +-------------- package.json | 2 +- package.postcss7.json | 2 +- src/lib/purgeUnusedStyles.js | 52 +++++++++++++++++++++++++++--------- 4 files changed, 43 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index e0033bbf2625..ae0c6a341184 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "2.1.2", "license": "MIT", "dependencies": { - "@fullhuman/postcss-purgecss": "^4.0.3", "arg": "^5.0.0", "bytes": "^3.0.0", "chalk": "^4.1.1", @@ -36,6 +35,7 @@ "postcss-selector-parser": "^6.0.6", "postcss-value-parser": "^4.1.0", "pretty-hrtime": "^1.0.3", + "purgecss": "^4.0.3", "quick-lru": "^5.1.1", "reduce-css-calc": "^2.1.8", "resolve": "^1.20.0" @@ -1504,17 +1504,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@fullhuman/postcss-purgecss": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-4.0.3.tgz", - "integrity": "sha512-/EnQ9UDWGGqHkn1UKAwSgh+gJHPKmD+Z+5dQ4gWT4qq2NUyez3zqAfZNwFH3eSgmgO+wjTXfhlLchx2M9/K+7Q==", - "dependencies": { - "purgecss": "^4.0.3" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -11339,14 +11328,6 @@ } } }, - "@fullhuman/postcss-purgecss": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@fullhuman/postcss-purgecss/-/postcss-purgecss-4.0.3.tgz", - "integrity": "sha512-/EnQ9UDWGGqHkn1UKAwSgh+gJHPKmD+Z+5dQ4gWT4qq2NUyez3zqAfZNwFH3eSgmgO+wjTXfhlLchx2M9/K+7Q==", - "requires": { - "purgecss": "^4.0.3" - } - }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", diff --git a/package.json b/package.json index 1e64035ca8d5..3bbace9b3338 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "postcss": "^8.0.9" }, "dependencies": { - "@fullhuman/postcss-purgecss": "^4.0.3", + "purgecss": "^4.0.3", "arg": "^5.0.0", "bytes": "^3.0.0", "chalk": "^4.1.1", diff --git a/package.postcss7.json b/package.postcss7.json index 99327f9062e7..dbe78312313d 100644 --- a/package.postcss7.json +++ b/package.postcss7.json @@ -1,6 +1,6 @@ { "dependencies": { - "@fullhuman/postcss-purgecss": "^3.1.3", + "purgecss": "^4.0.3", "autoprefixer": "^9", "postcss": "^7", "postcss-functions": "^3", diff --git a/src/lib/purgeUnusedStyles.js b/src/lib/purgeUnusedStyles.js index 76bee22e18b9..95d75e5765aa 100644 --- a/src/lib/purgeUnusedStyles.js +++ b/src/lib/purgeUnusedStyles.js @@ -1,6 +1,6 @@ import _ from 'lodash' import postcss from 'postcss' -import purgecss from '@fullhuman/postcss-purgecss' +import PurgeCSS, { defaultOptions, standardizeSafelist, mergeExtractorSelectors } from 'purgecss' import log from '../util/log' import htmlTags from 'html-tags' import path from 'path' @@ -141,10 +141,11 @@ export default function purgeUnusedUtilities( registerDependency(parseDependency(fileOrGlob)) } + let hasLayers = false + + const mode = _.get(config, 'purge.mode', 'layers') return postcss([ function (css) { - const mode = _.get(config, 'purge.mode', 'layers') - if (!['all', 'layers'].includes(mode)) { throw new Error('Purge `mode` must be one of `layers` or `all`.') } @@ -171,6 +172,7 @@ export default function purgeUnusedUtilities( switch (comment.text.trim()) { case `tailwind start ${layer}`: comment.text = 'purgecss end ignore' + hasLayers = true break case `tailwind end ${layer}`: comment.text = 'purgecss start ignore' @@ -185,14 +187,40 @@ export default function purgeUnusedUtilities( css.append(postcss.comment({ text: 'purgecss end ignore' })) }, removeTailwindMarkers, - purgecss({ - defaultExtractor: (content) => { - const transformer = getTransformer(config) - return defaultExtractor(transformer(content)) - }, - extractors: fileSpecificExtractors, - ...purgeOptions, - content, - }), + + async function (css) { + if (mode === 'layers' && !hasLayers) { + return + } + const purgeCSS = new PurgeCSS() + purgeCSS.options = { + ...defaultOptions, + + defaultExtractor: (content) => { + const transformer = getTransformer(config) + return defaultExtractor(transformer(content)) + }, + extractors: fileSpecificExtractors, + ...purgeOptions, + safelist: standardizeSafelist(purgeOptions.safelist), + } + + const fileFormatContents = content.filter((o) => typeof o === 'string') + const rawFormatContents = content.filter((o) => typeof o === 'object') + + const cssFileSelectors = await purgeCSS.extractSelectorsFromFiles( + fileFormatContents, + purgeCSS.options.extractors + ) + const cssRawSelectors = await purgeCSS.extractSelectorsFromString( + rawFormatContents, + purgeCSS.options.extractors + ) + const cssSelectors = mergeExtractorSelectors(cssFileSelectors, cssRawSelectors) + purgeCSS.walkThroughCSS(css, cssSelectors) + if (purgeCSS.options.fontFace) purgeCSS.removeUnusedFontFaces() + if (purgeCSS.options.keyframes) purgeCSS.removeUnusedKeyframes() + if (purgeCSS.options.variables) purgeCSS.removeUnusedCSSVariables() + }, ]) } From c3c472113f83c398766e373bc4425e4e923f2be0 Mon Sep 17 00:00:00 2001 From: Sergey Tatarintsev Date: Mon, 28 Jun 2021 17:13:37 +0200 Subject: [PATCH 2/2] fix: purgecss should respect safelist.variables --- src/lib/purgeUnusedStyles.js | 4 +++ tests/purgeUnusedStyles.test.js | 61 +++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/lib/purgeUnusedStyles.js b/src/lib/purgeUnusedStyles.js index 95d75e5765aa..0bf28487f07b 100644 --- a/src/lib/purgeUnusedStyles.js +++ b/src/lib/purgeUnusedStyles.js @@ -205,6 +205,10 @@ export default function purgeUnusedUtilities( safelist: standardizeSafelist(purgeOptions.safelist), } + if (purgeCSS.options.variables) { + purgeCSS.variablesStructure.safelist = purgeCSS.options.safelist.variables || [] + } + const fileFormatContents = content.filter((o) => typeof o === 'string') const rawFormatContents = content.filter((o) => typeof o === 'object') diff --git a/tests/purgeUnusedStyles.test.js b/tests/purgeUnusedStyles.test.js index ad249233a3c8..8a2d68f8b950 100644 --- a/tests/purgeUnusedStyles.test.js +++ b/tests/purgeUnusedStyles.test.js @@ -639,6 +639,67 @@ test( }) ) +test('purges unused css variables in "all" mode', () => { + return inProduction( + suppressConsoleLogs(() => { + return postcss([ + tailwind({ + ...config, + purge: { + mode: 'all', + content: [path.resolve(`${__dirname}/fixtures/**/*.html`)], + options: { + variables: true, + }, + }, + }), + ]) + .process( + ` + :root { + --unused-var: 1; + } + ` + ) + .then((result) => { + expect(result.css).not.toContain('--unused-var') + }) + }) + ) +}) + +test('respects safelist.variables in "all" mode', () => { + return inProduction( + suppressConsoleLogs(() => { + return postcss([ + tailwind({ + ...config, + purge: { + mode: 'all', + content: [path.resolve(`${__dirname}/fixtures/**/*.html`)], + options: { + variables: true, + safelist: { + variables: ['--unused-var'], + }, + }, + }, + }), + ]) + .process( + ` + :root { + --unused-var: 1; + } + ` + ) + .then((result) => { + expect(result.css).toContain('--unused-var') + }) + }) + ) +}) + test('element selectors are preserved by default', () => { return inProduction( suppressConsoleLogs(() => {