A better way to integrate tailwindcss and purgecss with any ember application
tl;dr, for those who don't care why it is better, just follow these steps:
- install all required dependencies:
$ yarn add tailwindcss @fullhuman/postcss-purgecss broccoli-source broccoli-postcss-single --dev
- prepare an entry css file and the tailwind config file:
# you can choose any path you like
$ mkdir -p app/tailwind
$ touch app/tailwind/index.css app/tailwind/config.js
the content of app/tailwind/index.css:
/* purgecss start ignore */
@tailwind base;
@tailwind components;
/* purgecss end ignore */
@tailwind utilities;
the content of app/tailwind/config.js is what exactly tailwindcss init command gives you, or you can change it whatever you like as long as the tailwindcss accepts it.
- update your
ember-cli-build.js:
const EmberApp = require("ember-cli/lib/broccoli/ember-app");
const { join } = require("path");
const { WatchedDir } = require("broccoli-source");
const compilePostCSS = require("broccoli-postcss-single");
module.exports = function(defaults) {
const app = new EmberApp(defaults, {
// other settings
});
const tailwindBase = join(__dirname, "app", "tailwindcss");
const plugins = [require("tailwindcss")(join(tailwindBase, "tailwind.js"))];
if (EmberApp.env() === "production") {
plugins.push({
module: require("@fullhuman/postcss-purgecss"),
options: {
content: [
join(__dirname, "app", "index.html"),
join(__dirname, "app", "**", "*.js"),
join(__dirname, "app", "**", "*.hbs")
],
defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || [],
whitelistPatterns: [/^_/]
}
});
}
const tailwindNode = compilePostCSS(
new WatchedDir(tailwindBase),
"index.css",
"assets/tailwind.css",
{ browsers, cacheInclude: [/.*\.(css|js)$/], plugins }
);
return app.toTree([tailwindNode]);
};
- boot up your app with
ember serve, after a short period of time you will see the output file indist/assets/tailwind.css. Now, open yourapp/index.html, add one line above where your app loads thevendor.css:
<link integrity="" rel="stylesheet" href="{{rootURL}}assets/tailwind.css" />
- Voila!
So why it is better?
The core difference is to separate the building process of tailwindcss completely from the main css building process, this will bring us some benefits:
-
The initial build is relatively faster (that depends on how complex your prior building process is)
-
Each rebuilding will extremely faster
Integrate tailwindcss in your main css building process will dramatically slow down your building efficiency, I have tested it in a newly created ember octane app, add tailwindcss process will increase at least 1~2 seconds for each rebuilding that caused by css changes, actually, we don't even need to rebuild tailwindcss again and again, it doesn't make any sense.
- Only rebuild the tailwindcss on config changes
By using a dedicated broccoli node to watch the changes of app/tailwind/config.js, the content of assets/tailwind.css only updates as needed.
- PostCSS is optional for the main css building process
PostCSS is good, but sometimes we only want to introduce tailwindcss with customization ability and without touching the existed css building process.
As step 4. shows, you can still do whatever you like to the styles in app/styles directory, PostCSS is just a (good) option.
- It emits an individual css file
I like to load the assets/tailwind.css file individually, and I know some ppl like it too. Tailwindcss is huge by default, even if purgecss can help us out, it still big especially you customize it a lot, so loading it separately can be good because it gives you some space to do more optimizations.
Hope this can help you enjoy working with tailwindcss and ember together.
Hi @nightire thanks very much for the detailed writeup.
It sounds very interesting and agree that the approach makes a lot of sense.
I’m going to try it on a project first and see how it works and then update the docs to reflect this change once I’m comfortable with it.
Thanks again.
I found this comment, and really like the approach. Slow builds for tweaking css can drag things down and make everything feel kind of slow.
For anyone else coming to this thread, the default extractor isn't the default from tailwind the details of which are listed here: https://tailwindcss.com/docs/controlling-file-size#setting-up-purge-css-manually
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)
Thanks for the writeup. I'm trying to update tailwind in my Ember project by phasing out ember-tailwind-cli.
The browsers variable is not defined before it is used in
const tailwindNode = compilePostCSS(
new WatchedDir(tailwindBase),
"index.css",
"assets/tailwind.css",
{ browsers, cacheInclude: [/.*\.(css|js)$/], plugins }
);
Is this an error or am I missing something?
Thanks for the writeup. I'm trying to update tailwind in my Ember project by phasing out ember-tailwind-cli.
The
browsersvariable is not defined before it is used inconst tailwindNode = compilePostCSS( new WatchedDir(tailwindBase), "index.css", "assets/tailwind.css", { browsers, cacheInclude: [/.*\.(css|js)$/], plugins } );Is this an error or am I missing something?
const { browsers } = require('./config/targets');
The configurations above didn't work with Tailwind's just in time (JIT) compilation, meaning I had to restart the Ember server every time I referenced a new Tailwind css class.
The approach suggested in this comment worked well for me. (I am not using embroider, and there are other comments in that thread dealing with embroider.)
To wrap it up into one place:
packages required:
npx ember install ember-cli-postcss
yarn add tailwindcss cssnano autoprefixer
ember-cli-build.js:
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const tailwindcss = require('tailwindcss');
const isProduction = EmberApp.env() === 'production';
module.exports = function (defaults) {
let app = new EmberApp(defaults, {
// your normal configuration here
postcssOptions: {
compile: {
cacheInclude: [/.*\.(css|scss|hbs)$/, /.tailwind\.config\.js$/],
map: false,
plugins: [
tailwindcss('./tailwind.config.js'),
autoprefixer,
...(isProduction ? [cssnano] : []),
],
},
},
});
tailwind.config.js:
/* global module*/
module.exports = {
// These are the folders that Tailwind will examine to determine
// which of its classes should be included in the copy of tailwind.css
// that gets distributed with our app
content: ['app/**/*.{html,js,hbs}'],
theme: {
extend: {},
},
plugins: [],
};
app.css
@tailwind base;
@tailwind components;
@tailwind utilities;
Location of files in folder structure:
./ember-cli-build.js
./tailwind.config.js
./app/css/app/css