tailwindcss/src/lib/purgeUnusedStyles.js
Navith d4bd2d0b05
Make purge still preserve HTML elements for user-defined extractors (#2704)
* Add failing test for purge preserving element selectors when `defaultExtractor` is overridden

* `preserveHtmlElements` works with user-defined purge extractors
2020-10-31 10:02:40 -04:00

122 lines
3.9 KiB
JavaScript

import _ from 'lodash'
import postcss from 'postcss'
import purgecss from '@fullhuman/postcss-purgecss'
import log from '../util/log'
import htmlTags from 'html-tags'
function removeTailwindMarkers(css) {
css.walkAtRules('tailwind', (rule) => rule.remove())
css.walkComments((comment) => {
switch (comment.text.trim()) {
case 'tailwind start base':
case 'tailwind end base':
case 'tailwind start components':
case 'tailwind start utilities':
case 'tailwind end components':
case 'tailwind end utilities':
comment.remove()
break
default:
break
}
})
}
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,
'purge.enabled',
config.purge !== false && config.purge !== undefined && process.env.NODE_ENV === 'production'
)
if (!purgeEnabled) {
return removeTailwindMarkers
}
// Skip if `purge: []` since that's part of the default config
if (Array.isArray(config.purge) && config.purge.length === 0) {
if (configChanged) {
log.warn([
'Tailwind is not purging unused styles because no template paths have been provided.',
'If you have manually configured PurgeCSS outside of Tailwind or are deliberately not removing unused styles, set `purge: false` in your Tailwind config file to silence this warning.',
'https://tailwindcss.com/docs/controlling-file-size/#removing-unused-css',
])
}
return removeTailwindMarkers
}
const { defaultExtractor, ...purgeOptions } = config.purge.options || {}
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`.')
}
if (mode === 'all') {
return
}
const layers = _.get(config, 'purge.layers', ['base', 'components', 'utilities'])
css.walkComments((comment) => {
switch (comment.text.trim()) {
case `purgecss start ignore`:
comment.before(postcss.comment({ text: 'purgecss end ignore' }))
break
case `purgecss end ignore`:
comment.before(postcss.comment({ text: 'purgecss end ignore' }))
comment.text = 'purgecss start ignore'
break
default:
break
}
layers.forEach((layer) => {
switch (comment.text.trim()) {
case `tailwind start ${layer}`:
comment.text = 'purgecss end ignore'
break
case `tailwind end ${layer}`:
comment.text = 'purgecss start ignore'
break
default:
break
}
})
})
css.prepend(postcss.comment({ text: 'purgecss start ignore' }))
css.append(postcss.comment({ text: 'purgecss end ignore' }))
},
removeTailwindMarkers,
purgecss({
content: Array.isArray(config.purge) ? config.purge : config.purge.content,
defaultExtractor: (content) => {
const extractor = defaultExtractor || tailwindExtractor
const preserved = [...extractor(content)]
if (_.get(config, 'purge.preserveHtmlElements', true)) {
preserved.push(...htmlTags)
}
return preserved
},
...purgeOptions,
}),
])
}