Using Laravel Mix, Tailwind and PurgeCSS to build a Grav theme

Using Laravel Mix, Tailwind and PurgeCSS to build a Grav theme

If you are around the PHP and Open Source community, you have probably at least heard of the utility-based CSS framework: Tailwind. The ability for developers with varying degrees of frontend chops to build custom user interfaces with little to no CSS is essentially unparalleled. In the off-chance you haven't, Tailwind describes themselves as:

Tailwind CSS is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override.
...
Instead of opinionated predesigned components, Tailwind provides low-level utility classes that let you build completely custom designs without ever leaving your HTML.

Being an application developer myself, you can probably understand why I am enamored with it. However, I will admit that it does have a bit of a learning curve. A good portion of it has to do with the initial setup and the recommended use of PurgeCSS; because of the extremely beefy (2380.4kB) base CSS file  that Tailwind ships with. So again, it is highly recommended that you use a post-css tool like PurgeCSS to remove unused classes and utilities from your builds.

Selecting a build tool

Despite all of my experience in the web/application/mobile development scenes, I will be the first to admit that I am NOT a Webpack expert. I know just enough to be effective but I do not know all of the ins and outs. Just like Tailwind, it can take a bit of work to get it to behave the way you want. So naturally I began looking for another option.

In case you didn't know, I am a Laravel application developer by trade. I love just about everything about the Laravel ecosystem. Yep, call me a fan boy:

One of the items that I absolutely love is Laravel Mix. This Node module is essentially a Webpack wrapper that makes Webpack far more accessible and cleaner with an optimized structure/API. As it turns out, you can use Laravel's front end build tool, Mix, in other non-Laravel environments. Golden.

Building a website for "techy" people

The whole inspiration for this post came from the need for a co-worker and I to build a simple marketing landing page for an application that we have been working on. Since this was intended to be a single page, we didn't need a fully baked CMS, but we also wanted to have the ability to update some content/images/etc without code changes.

My recommendation was to use Grav. This CMS, in my opinion, is one of the better lightweight and flat file CMS' out there. It does not require a lot of server resources (namely a database) to run and can be saved via version control. With all of that said, it can have a bit of learning curve and may not be super easy to use for people who do not have a lot of CMS experience. That's why I do not heavily recommend this CMS to all my clients. Still, there is a ton that you can do with it. This is because it relies pretty heavily on YAML and Markdown; making Grav a perfect solution for "technologically inclined" people.

The build

So we decided to use Grav and to build a theme for it, using Tailwind. The Grav documentation is pretty good, but they do not really include a bunch of information how to use SCSS or any other type of pre/post processing in the building of your theme. So what you see below is the result of my trial and error.

1. Set up Grav

There are a couple ways to install Grav. I will send you over to their docs so you can follow them. No sense in duplicate content.

2. Create theme

First things first, we need to install the Grav devtools. This can be used via the Grav Package Manager (GPM). Run this command within your Grav directory:

bin/gpm install devtools

Note: This does not need to be install globally. The GPM is part of any installation.

Next lets generate the theme:

bin/plugin devtools new-theme

This process will ask you a few questions that are required to create the new theme and will compile them into your theme's YAML configuration file. Activate the new theme in the system configuration and we are good to go

3. Mix and other dependencies

Now that the theme has been created, let's start installing some stuff. Within your new theme directory, create a package.json file in the theme's root directory with the following content:

{
    "private": true,
    "scripts": {
        "dev": "npm run development",
        "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch": "npm run development -- --watch",
        "watch-poll": "npm run watch -- --watch-poll",
        "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --disable-host-check --config=node_modules/laravel-mix/setup/webpack.config.js",
        "prod": "npm run production",
        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
    },
    "dependencies": {
        "tailwindcss": "^1.6.2"
    },
    "devDependencies": {
        "cross-env": "^7.0.2",
        "laravel-mix": "^5.0.4",
        "laravel-mix-purgecss": "^5.0.0",
        "node-sass": "^4.14.1",
        "sass-loader": "^8.0.2"
    }
}
If you don't plan on using SCSS/SASS feel free to leave off node-sass and sass-loader

Next in the same location create a webpack.mix.js file with:

const mix = require('laravel-mix')
const tailwindcss = require('tailwindcss')
require('laravel-mix-purgecss');

mix.sass('scss/app.scss', 'css/custom.css')
    .sourceMaps()
    .options({
        processCssUrls: false,
        postCss: [
            tailwindcss('tailwind.config.js')
        ],
    })
    .purgeCss({
        enabled: mix.inProduction(),
        content: [
            `./templates/**/*.twig`,
            `./scss/**/*.scss`
        ],
        folders: ['js', 'scss', 'templates'],
        extensions: ['html', 'js', 'twig', 'scss'],
        whitelistPatterns: [
            //
        ],
    });
Again, if you are not planning on using SCSS/SASS change: mix.sass('scss/app.scss', 'css/custom.css') to mix.css('css/app.css', 'css/custom.css')

4. Setting up Tailwind

Im sure you noticed in the mention of tailwind via tailwindcss('tailwind.config.js'). Until now we haven't created it yet. Let's do that now. In the same directory as the package.json file, create tailwind.config.js with the following content:

const defaultTheme = require('tailwindcss/defaultTheme')

module.exports = {
  purge: [],
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}

I won't go into much detail about how to configure Tailwind as it is pretty well spelled out in their documentation. This is all we need to get started at least.

5. Modifying the templates

Up until now we have been creating the necessary files and workflows to generate our CSS to render in our theme, but we haven't actually done anything with our theme yet. When you created the new theme, a ./templates/partials/base.html.twig file should have been generated. You should see something like the following within the <head> tag:

{% set theme_config = attribute(config.themes, config.system.pages.theme) %}
<!DOCTYPE html>
<html lang="{{ grav.language.getActive ?: grav.config.site.default_lang }}">
<head>
{% block head %}
    <meta charset="utf-8" />
    <title>{% if header.title %}{{ header.title|e('html') }} | {% endif %}{{ site.title|e('html') }}</title>

    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    {% include 'partials/metadata.html.twig' %}

    <link rel="icon" type="image/png" href="{{ url('theme://images/logo.png') }}" />
    <link rel="canonical" href="{{ page.url(true, true) }}" />
{% endblock head %}

{% block stylesheets %}
    {% do assets.addCss('https://unpkg.com/purecss@1.0.0/build/pure-min.css', 100) %}
    {% do assets.addCss('https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css', 99) %}
{% endblock %}

{% block javascripts %}
    {% do assets.addJs('jquery', 100) %}
{% endblock %}

{% block assets deferred %}
    {{ assets.css()|raw }}
    {{ assets.js()|raw }}
{% endblock %}
</head>

I want to direct your attention to the {% block stylesheets %} tag. Within there are a few references to some css files. The numbers referenced, 100 and 99 respectively, are the order in which these files will be minified during run time. Since we will be using a custom built stylesheet utilizing Tailwind and PurgeCSS, we can now update the {% block stylesheets %} tag to show the following:

{% block stylesheets %}
    {% do assets.addCss('theme://css/custom.css', 100) %}
{% endblock %}

6. Compiling Tailwind

All that is left to do is build our CSS. If we use npm run dev or yarn dev, you will see a relatively large file being output to css/custom.css. That is because PurgeCSS will only run when Webpack is in production mode. If you use npm run prod or yarn prod, you will notice that the CSS file is exponentially smaller. That is because PurgeCSS will comb through all of your project's files and look for the use of Tailwind's CSS. If it is not being used, it will be purged; effectively making your website's assets much lighter weight.

That's it

Hopefully you found some of this enlightening. Tailwind has revolutionized how I develop and code; regardless if it is an application or website. Hopefully it will become useful for you as well.

Show Comments