I’ve started working with Tailwindcss recently and I’m loving it so far. This is a short write-up of how I’ve set everything up in my project.
First I’ve added tailwind into my project dependencies:
npm install --save-dev tailwindcss
Next I created a css file and called it tailwind.css
which contains the following:
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
This is the file that is used by tailwind to generate an output css file which I will use in my app. You can also add your own utility styles/classes in between the imports (more info on that here), although since I’m using tailwind I honestly have not needed it (yet).
Ok at this point I was ready to run tailwind and have it generate an output css file.
npx tailwindcss build css/tailwind.css -o css/tailwind.generated.css
Unfortunately this generates an output file that is quite large in size. That’s because by default tailwind includes every single css class variation. The good news is that, tailwind now includes an option that can purge unused classes, leaving you with only the ones that you use and reducing the size of the output file dramatically (in my app, it went down to 12KB!).
To setup purging I created a tailwind config file using the tailwind cli:
npx tailwindcss init
This will create tailwind.config.js
, which can be used to configure and customize tailwind. By default it has the following contents:
module.exports = {
theme: {},
variants: {},
plugins: [],
}
To enable purging, I changed it to:
module.exports = {
// This points to the folder that contains my react components
purge: ["./App/**/*.tsx", "./App/**/*.jsx"],
// By default purging won't work on dev (NODE_ENV), if you want to force it, use the below
// enabled: true,
theme: {},
variants: {},
plugins: [],
}
Furthermore, I’ve modified the config file to include some helper classes for dark mode and some extra spacing sizes (allowing me to do <div className="bg-gray-100 dark:bg-gray-900">
):
module.exports = {
purge: ["./App/**/*.tsx"],
// By default purging won't work on dev (NODE_ENV), if you want to force it, use the below
// enabled: true,
theme: {
extend: {
screens: {
dark: { raw: "(prefers-color-scheme: dark)" }
// => @media (prefers-color-scheme: dark) { ... }
},
spacing: {
"72": "18rem",
"84": "21rem",
"96": "24rem"
}
}
},
variants: {},
plugins: []
};
Now that purging is configured, I modified my package.json
to incorporate tailwind into my build:
{
//...
"scripts": {
"build:tailwind-dev": "tailwindcss build src/css/tailwind.css -o src/css/tailwind.generated.css",
"build:tailwind-prod": "cross-env NODE_ENV=production tailwindcss build src/css/tailwind.css -o src/css/tailwind.generated.css",
"prestart": "npm run build:tailwind-dev",
"prebuild": "npm run build:tailwind-prod",
// ...
},
// ...rest of package.json
}
This was pretty easy to setup and the output is significantly smaller. Great! The problem was that the output was not minified, and I felt I could/had to optimize it even further. Unfortunately as of the time of writing this, tailwind doesn’t support minimization the same way it supports purging (i.e. a config setting).
A way to fix this is to backtrack a little bit remove purging from the tailwind.config.js
file and set it up using PostCSS instead.
I referred to the official tailwind documentation.
First, I installed the postcss-cli
tools as well as autoprefixer
(which is a postcss plugin that automatically adds vendor prefixes (-moz-
, -webkit-
etc.) to css classes):
npm install --save-dev postcss-cli autoprefixer
Then I created a postcss.config.js
file, as follows:
module.exports = {
plugins: [
require("tailwindcss"),
require("autoprefixer")
]
};
Now I tried to run the following command and see what the output would be:
npx cross-env NODE_ENV=production postcss src/css/tailwind.css -o src/css/tailwind.generated.css
An output file was built, but not purged (as expected, since I didn’t enable it yet). In order to to that I included PurgeCSS as a dev dependency and included it in my postcss.config.js
file. In fact, that’s what tailwind is doing under the hood for its purge
option from earlier.
npm install --save-dev @fullhuman/postcss-purgecss
Then in postcss.config.js
:
const purgecss = require("@fullhuman/postcss-purgecss")({
// Specify the paths to all of the template files in your project
content: [
"./src/**/*.tsx"
// etc.
],
// This is the function used to extract class names from your templates
defaultExtractor: (content) => {
// Capture as liberally as possible, including things like `h-(screen-1.5)`
const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [];
// Capture classes within other delimiters like .block(class="w-1/2") in Pug
const innerMatches = content.match(/[^<>"'`\s.()]*[^<>"'`\s.():]/g) || [];
return broadMatches.concat(innerMatches);
}
});
module.exports = {
plugins: [
require("tailwindcss"),
require("autoprefixer"),
// Include purgecss only on production
...(process.env.NODE_ENV === "production" ? [purgecss] : [])
]
};
In addition I had to modify the tailwind.css
so that PurgeCSS would ignore some base tailwind styles.
/* purgecss start ignore */
@import "tailwindcss/base";
@import "tailwindcss/components";
/* purgecss end ignore */
@import "tailwindcss/utilities";
If I now run this again:
npx cross-env NODE_ENV=production postcss src/css/tailwind.css -o src/css/tailwind.generated.css
The output is purged as expected. Great!
Next thing I did was add minification. I used cssnano:
npm install --save-dev cssnano
And then I modified our postcss.config.js
to include it:
// Purge css classes that are not being used
const purgecss = require("@fullhuman/postcss-purgecss")({
// Specify the paths to all of the template files in your project
content: [
"./src/**/*.tsx"
// etc.
],
// This is the function used to extract class names from your templates
defaultExtractor: (content) => {
// Capture as liberally as possible, including things like `h-(screen-1.5)`
const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [];
// Capture classes within other delimiters like .block(class="w-1/2") in Pug
const innerMatches = content.match(/[^<>"'`\s.()]*[^<>"'`\s.():]/g) || [];
return broadMatches.concat(innerMatches);
}
});
// Minification of CSS
const cssnano = require("cssnano")({
preset: "default"
});
module.exports = {
plugins: [
require("tailwindcss"),
require("autoprefixer"),
// Here we include both purging and minification
...(process.env.NODE_ENV === "production" ? [purgecss, cssnano] : [])
]
};
Now I tried running this again:
npx cross-env NODE_ENV=production postcss src/css/tailwind.css -o src/css/tailwind.generated.css
The output file was purged and minimized (and tiny! - about 4KB down from 12KB).
One thing left to do was to modify my package.json
to use the postcss-cli
rather than the tailwind-cli
:
{
//...
"scripts": {
"build:tailwind-dev": "postcss src/css/tailwind.css -o src/css/tailwind.generated.css",
"build:tailwind-prod": "cross-env NODE_ENV=production postcss src/css/tailwind.css -o src/css/tailwind.generated.css",
// ...
}
//...
}
At this point I was ready to go with my tailwind setup with purging and minimization and all the jazz.