TailwindCSS is an amazing tool, but the extraneous utility classes it generates can bloat builds. That’s where PurgeCSS comes in, removing unused classes from our CSS output.
PurgeCSS has sharp edges though—correct usage requires care, and just the right configuration. Here I’ll try to document the caveats of using it with TailwindCSS in your Vue.js project, and save you some trouble.
tl;dr
Use an existing vue-cli plugin (or copy its config into your project). They should take care of configuring things correctly, which saves you from worrying about Tailwind- and Vue-specific caveats. You can find several on GitHub to choose from, but the only I know of that addresses these issues is my personal one: https://github.com/ky-is/vue-cli-plugin-tailwind.
Universal PurgeCSS caveats
Include all files that consume CSS in the content
array
PurgeCSS searches the content
paths you provide to find all the CSS classes in-use by your project. For Vue, we want to include './public/**/*.html'
and './src/**/*.vue'
. If you end up referencing classnames from other filetypes like JS, you need to include those as well (i.e.'./src/**/*.js'
).
Never string-concatinate classnames
PurgeCSS works by regex pattern matching your code for classes to whitelist. This means classnames must always be written out in their full form:
Don’t: `lights-${isOn ? 'on' : 'off'}`
Do: isOn ? 'lights-on' : 'lights-off'
In the Don’t example, PurgeCSS will only find lights-
(since $ is not included in the classname regex), so these classes will be incorrectly purged.
Universal Vue.js + TailwindCSS caveats
Class conflicts
Don’t create a <transition-group>
with name="cursor"
. This will unexpectedly inherit Tailwind’s .cursor-move
utility while move transitions are in progress. (See https://vuejs.org/v2/guide/transitions.html#List-Move-Transitions.)
External code that consumes classes
If we add a 3rd-party component to our code that requires importing CSS into our project, PurgeCSS again can’t find these classes in use, and will purge them. There’s two different ways to solve this. We’ll take vue-good-table as an example:
- Include the library’s source code in PurgeCSS’s
content
array, so that it sees these classes in use:'./node_modules/vue-good-table/src/**/*.vue'
. - Alternatively, whitelist all of the classes included by the library. vue-good-table prefixes its classes, so we can add
/^vgt-/
to thewhitelistPatterns
array.
TailwindCSS caveats
Special characters in classnames
Tailwind uses :
and /
in its utility classes, so we need to customize PurgeCSS’s regex with a custom extractor (see the final configuration). Tailwind’s docs recommend /[A-Za-z0-9-_:/]+/g
.
But Vue.js has a common class binding object syntax, as in :class="{ active: isActive }"
. With our above regex, PurgeCSS matches active:
instead of active
, resulting in .active { ... }
being incorrectly purged from our CSS.
The fix is to skip the terminal :
. We can do that by updating the regex to /[A-Za-z0–9-_/:]*[A-Za-z0–9-_/]+/g
.
Vue.js caveats
Single-file components contain <style> blocks
When we search a .vue
file for class names, it will find styles defined in <style>
blocks and add them to the whitelist, even though they may never actually be used in our app’s HTML. The solution is to remove the style block when searching content in our extractor: content.replace(/<style[^]+?<\/style>/gi, '')
(see the final configuration).
Autogenerated classnames
Vue.js automatically applies many classes for<transition>
, <transition-group>
, and <router-link>
components. PurgeCSS cannot see these used outside our CSS, so they’ll be purged by default.
The solution is to add the following whitelistPatterns
to our PurgeCSS configuration:
/-(leave|enter|appear)(|-(to|from|active))$/
supports<transition>
and<transition-group>
default enter/leave classes.^(?!cursor-move).+-move$
supports<transition-group>
move classes (without whitelisting TailwindCSS’s.cursor-move
utility)./^router-link(|-exact)-active$/
supports Vue Router’s default classes. If you aren’t using vue-router, you can skip this one.
Final configuration
As a PostCSS plugin:
require('@fullhuman/postcss-purgecss')({
content: [ './public/**/*.html', './src/**/*.vue' ],
defaultExtractor: (content) => {
const contentWithoutStyleBlocks = content.replace(/<style[^]+?<\/style>/gi, '')
return contentWithoutStyleBlocks.match(/[A-Za-z0-9-_/:]*[A-Za-z0-9-_/]+/g) || []
},
whitelistPatterns: [ /-(leave|enter|appear)(|-(to|from|active))$/, /^(?!cursor-move).+-move$/, /^router-link(|-exact)-active$/ ],
})
(See https://github.com/ky-is/vue-cli-plugin-tailwind/blob/master/generator/templates/postcss.config.js for a complete example.)
That’s probably more than you ever wanted to know about combining PurgeCSS, Vue.js, and TailwindCSS. Use an existing, battle-tested configuration as a starting point, and be mindful of those universal caveats whenever using PurgeCSS. Save yourself from styles mysteriously not working in production.