Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve production build performance for the case of many small non-tailwind stylesheets #4644

Merged
merged 2 commits into from Jul 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion 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",
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 @@ -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`.')
}
Expand 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'
Expand All @@ -185,14 +187,44 @@ 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),
}

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