Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kritoke/6794141e070e6d0554037103b9271cdb to your computer and use it in GitHub Desktop.
Save kritoke/6794141e070e6d0554037103b9271cdb to your computer and use it in GitHub Desktop.
Add Tailwind CSS to an Elixir/Phoenix Project with PurgeCSS

Thanks to the original blog post: https://equimper.com/blog/how-to-setup-tailwindcss-in-phoenix-1.4

1. Install tailwindcss and postcss-loader from npm:

cd assets
npm i --save-dev tailwindcss postcss-loader postcss-import

2. Initialize tailwind (still in assets directory)

npx tailwind init

3. Create assets/postcss.config.js file and add contents:

module.exports = {
  plugins: [
    require('postcss-import')(),
    require('tailwindcss')('./tailwind.config.js'),
    require('autoprefixer'),
  ],
};

4. Update assets/webpack.config.js:

// assets/webpack.config.js

const path = require('path');
const glob = require('glob');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = (env, options) => ({
  optimization: {
    minimizer: [
      new UglifyJsPlugin({ cache: true, parallel: true, sourceMap: false }),
      new OptimizeCSSAssetsPlugin({}),
    ],
  },
  entry: {
    './js/app.js': ['./js/app.js'].concat(glob.sync('./vendor/**/*.js')),
  },
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, '../priv/static/js'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.css$/,
-         use: [MiniCssExtractPlugin.loader, 'css-loader']
+         use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({ filename: '../css/app.css' }),
    new CopyWebpackPlugin([{ from: 'static/', to: '../' }]),
  ],
});

5. Override your assets/css/app.css:

/** assets/css/app.css */

/**
 * This injects Tailwind's base styles, which is a combination of
 * Normalize.css and some additional base styles.
 *
 * If using `postcss-import`, use this import instead:
 *
 * @import "tailwindcss/base";
 */
 @tailwind base;

 /**
  * This injects any component classes registered by plugins.
  *
  * If using `postcss-import`, use this import instead:
  *
  * @import "tailwindcss/components";
  */
 @tailwind components;

 /**
  * Here you would add any of your custom component classes; stuff that you'd
  * want loaded *before* the utilities so that the utilities could still
  * override them.
  *
  * Example:
  *
  * .btn { ... }
  * .form-input { ... }
  *
  * Or if using a preprocessor or `postcss-import`:
  *
  * @import "components/buttons";
  * @import "components/forms";
  */

 /**
  * This injects all of Tailwind's utility classes, generated based on your
  * config file.
  *
  * If using `postcss-import`, use this import instead:
  *
  * @import "tailwindcss/utilities";
  */
 @tailwind utilities;

 /**
  * Here you would add any custom utilities you need that don't come out of the
  * box with Tailwind.
  *
  * Example :
  *
  * .bg-pattern-graph-paper { ... }
  * .skew-45 { ... }
  *
  * Or if using a preprocessor or `postcss-import`:
  *
  * @import "utilities/background-patterns";
  * @import "utilities/skew-transforms";
  */

6. PurgeCSS

With PurgeCSS you can reduce your CSS asset from approx ~300KB to under 10KB. This will improve the performance of your site.

cd assets
npm i -D purgecss-webpack-plugin glob-all

Then in your webpack.config.js

// assets/webpack.config.js

const path = require('path');
const glob = require('glob');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
+ const PurgecssPlugin = require('purgecss-webpack-plugin');
+ const globAll = require('glob-all');

+ // Custom PurgeCSS extractor for Tailwind that allows special characters in
+ // class names.
+ // Regex explanation: https://tailwindcss.com/docs/controlling-file-size/#understanding-the-regex
+ const TailwindExtractor = content => {
+   return content.match(/[\w-/:]+(?<!:)/g) || [];
+ };

module.exports = (env, options) => ({
  optimization: {
    minimizer: [
      new UglifyJsPlugin({ cache: true, parallel: true, sourceMap: false }),
      new OptimizeCSSAssetsPlugin({}),
+        new PurgecssPlugin({
+          paths: globAll.sync([
+            '../lib/<APP_NAME>_web/templates/**/*.html.eex',
+            '../lib/<APP_NAME>_web/views/**/*.ex',
+            '../assets/js/**/*.js',
+          ]),
+          extractors: [
+            {
+              extractor: TailwindExtractor,
+              extensions: ['html', 'js', 'eex', 'ex'],
+            },
+          ],
+        }),
    ],
  },
  entry: {
    './js/app.js': ['./js/app.js'].concat(glob.sync('./vendor/**/*.js')),
  },
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, '../priv/static/js'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({ filename: '../css/app.css' }),
    new CopyWebpackPlugin([{ from: 'static/', to: '../' }]),
  ],
});

Note: PurgeCSS will remove css classes you wrote to style HTML that comes from outside your code base (like a hex package, i.e pagination library). In those cases explicitly tell PurgeCSS to not remove them with:

/* purgecss start ignore */

.pagination-class-in-your-css-file {
  background-color: #fff;
}

/* purgecss end ignore */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment