Skip to content

Commit

Permalink
Improve production build performance for the case of many small non-t…
Browse files Browse the repository at this point in the history
…ailwind stylesheets (#4644)

* 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.

* fix: purgecss should respect safelist.variables
  • Loading branch information
SevInf committed Jul 1, 2021
1 parent bf48211 commit 2166b76
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 34 deletions.
21 changes: 1 addition & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -70,7 +70,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",
Expand Down
2 changes: 1 addition & 1 deletion package.postcss7.json
Expand Up @@ -3,7 +3,7 @@
"cssnano": "^4"
},
"dependencies": {
"@fullhuman/postcss-purgecss": "^3.1.3",
"purgecss": "^4.0.3",
"autoprefixer": "^9",
"postcss": "^7",
"postcss-functions": "^3",
Expand Down
56 changes: 44 additions & 12 deletions 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'
Expand Down Expand Up @@ -134,10 +134,11 @@ export default function purgeUnusedUtilities(config, configChanged, registerDepe
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`.')
}
Expand All @@ -164,6 +165,7 @@ export default function purgeUnusedUtilities(config, configChanged, registerDepe
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'
Expand All @@ -178,14 +180,44 @@ export default function purgeUnusedUtilities(config, configChanged, registerDepe
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),
}

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')

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()
},
])
}
61 changes: 61 additions & 0 deletions tests/purgeUnusedStyles.test.js
Expand Up @@ -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(() => {
Expand Down

0 comments on commit 2166b76

Please sign in to comment.