Using TailwindCSS in Common Lisp web apps without Node.js tooling
Tags: commonlisp, lisp, tailwindcs, css
Published: 2022-08-19 05:22:00


In this post, we are going to take a look at how to use [TailwindCSS](https://tailwindcss.com) like utility class CSS library in a Common Lisp web application without using any Node.js or JavaScript tooling. 

I have already written a post about using TailwindCSS in Common Lisp, but the tooling setup is kind of messy. You need to have Node.js installed in your machine. You need to install additional npm libraries and setup some config around it to make Tailwind CSS work in your Common Lisp web applications. 

{% link https://dev.to/rajasegar/building-common-lisp-web-apps-with-tailwind-css-5gjp %}

But in this post, we are going to look at a cleaner and simple way to use the CSS utility classes in a Common Lisp web application using the native platform capabilities of Common Lisp and its tooling ecosystem.

## cl-djula-tailwind
[cl-djula-tailwind]() is a Common Lisp package which helps you to use TailwindCSS classes in your Djula templates without any JavaScript or Node.js tooling


### Create a new Caveman project
We are going to create a new Caveman project in Common Lisp. So fire up your favourite Lisp interpreter and type the below commands to setup a new Common Lisp web application project using Caveman.

```lisp
(ql:quickload :caveman2)
(caveman2:make-project #P"~/quicklisp/local-projects/cl-tw-demo")
```
If you want to know more about building Common Lisp web applications you can refer to my previous post.

{% link https://dev.to/rajasegar/lisp-for-the-web-5-13ca %}

Now we need to add `cl-djula-tailwind` as system dependency for our newly created project. Open your system definition file for the project called `cl-tw-demo.asd` and add `cl-djula-tailwind` to the `:depends-on` section.

```lisp
(defsystem "cl-tw-demo"
  :version "0.1.0"
  :author "Rajasegar Chandran"
  :license ""
  :depends-on ("clack"
               "lack"
               "caveman2"
               "envy"
               "cl-ppcre"
               "uiop"

               ;; for @route annotation
               "cl-syntax-annot"

               ;; HTML Template
               "djula"

               ;; for DB
               "datafly"
               "sxql"

		;; Djula Tailwind
		"cl-djula-tailwind")
  :components ((:module "src"
                :components
                ((:file "main" :depends-on ("config" "view" "db"))
                 (:file "web" :depends-on ("view"))
                 (:file "view" :depends-on ("config"))
                 (:file "db" :depends-on ("config"))
                 (:file "config"))))
  :description ""
  :in-order-to ((test-op (test-op "cl-tw-demo-test"))))

```

## Injecting the CSS in templates
Add a placeholder for the stylesheet generated by `cl-djula-tailwind` in the default template templates/layouts/default.html. We also need to add the `safe` filter to the `tailwind` variable to mark it string as not requiring further HTML escaping prior to output. Otherwise Djula will try to escape or encode the quotes and other symbols in the generated css. More information about the `safe` filter can be found in the Djula [documentation](http://mmontone.github.io/djula/djula/Filters.html#safe-escape)

```html
<!DOCTYPE html>
  <html class="h-full bg-gray-100">
<head>
  <meta charset="utf-8">
  <title>{% block title %}{% endblock %}</title>
  <link rel="stylesheet" type="text/css" media="screen" href="/css/main.css">
	<style>{{ tailwind | safe }}</style>
</head>
```

## Generate the stylesheet
In order to generate the stylehsheet for our route templates, we are going to call the default exported function `get-stylesheet` from `cl-djula-tailwind` and give it the template name and the template directory as arguments.

And we need to set the value for the template variable `tailwind` which we have already used in the default layout template previously. This variable is going to be modified for each and every route, since the CSS for every route is going to be generated on the fly and sent to the template. So we need to make use the `*default-template-arugments*`.

In Djula, you can use the `*DEFAULT-TEMPLATE-ARGUMENTS*` variable to store arguments that will be available for all templates. It is a plist, so use getf to add arguments, like this:

```lisp
(setf (getf djula:*default-template-arguments* :tailwind) 'some-value)
```
And now, you can access `{{ tailwind }}` in your template.

This is useful when you have many templates that rely on the same set of variables. In our case the variable is the generated stylesheet using Tailwind classes.

You can find more information about default template arguments in the Djula [documentation](http://mmontone.github.io/djula/djula/Variables.html#Default-template-variables)  
 
Now define a new function called `render-stylesheet` in `src/web.lisp` like below

```lisp
(defun render-stylesheet (template)
	(setf (getf djula:*default-template-arguments* :tailwind) (cl-djula-tailwind:get-stylesheet template *template-directory*)))
```

## Routes
The next step is to tell our web application to render the generated stylesheet into our templates. Call the `render-stylesheet` function in your routes with the template name as the argument before the standard `render` function of Djula.

It is important that we need to call this function before `render` because we need to ensure that the generated stylesheet content is set in the default template arguments for Djula.

```lisp
(defroute "/" ()
  (render-stylesheet #P"index.html")
  (render #P"index.html"))
```

## See the demo app in action
Now load our demo app and start the server to see the web app in action to look at how our Tailwind CSS generated on the fly works

```lisp
(ql:quickload :cl-tw-demo)
(cl-tw-demo:start :port 3000)
```
Below are some of the sample screenshots of the demo app.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s2xdid7dv2jt63u0anuf.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e22djtoi8ku1pz3lpx2z.png)


## How it works

Let's see how this thing works. Whenever you request a page in your web application, Djula will try to read your route template and compile it and then send the HTML response to the browser. This is how normally things work in a Caveman application. On top of this what the `cl-djula-tailwind` package does is, it also parse the Tailwind class names used in your templates, layout templates and partials used in your markup and then construct a minified CSS class definitions list and put them in a  placeholder in the default layout template through a `<style>` tag so that it will be automatically picked up by the browser and your HTML content is properly formatted according to the CSS classes you have used.

The CSS is generated using another Common Lisp package called [cl-css](https://github.com/inaimathi/cl-css) and minified using [cl-minify-css](https://github.com/noloop/cl-minify-css). The class names from the template files are parsed using [cl-ppcre](https://github.com/edicl/cl-ppcre) using various regular expression patterns for different class names ranging from simple, responsive, pseudo-classes, dark mode and so on.

So whenever every route is rendered, you will get a generated custom CSS on the fly injected via internal stylesheets. The beauty of this approach is that you will only get the minimal and required CSS for a particular route instead of getting all the CSS at once for your whole application. From a performance standpoint this is a huge gain from the traditional approach of delivering all your CSS beforehand. Because the [critical rendering path](https://developer.mozilla.org/en-US/docs/Web/Performance/Critical_rendering_path) can be optimized to a large extent by delivering only the required CSS for a web page.

Using the CSS Overview extension in Chrome browser, I have collected some stats of the unused CSS. Below is the data of the TailwindCSS website 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ck7tt771j5g4m03yuqv.png)

You can still see some unused CSS with the conventional approach. And using `cl-djula-tailwind` in our demo app, this is the result

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ti5cvd47uqqrjnjy75xz.png)

With our new approach of sending only the required CSS, there are no unused CSS at all.

## Source code
The source code for the package `cl-djula-tailwind` is hosted in Github [here](https://github.com/rajasegar/cl-djula-tailwind).

## Demo
There is also a demo app making use the `cl-djula-tailwind` package to test and improve the package. It is hosted in Github [here](https://github.com/rajasegar/cl-tw-demo)

## What's next?
The `cl-djula-tailwind` package is still in beta stage and not yet ready for production. But you can play around with it. Most of the util classes used in Tailwind are covered, but still there is some work needs to be done to get it production ready. 

Below are some of the things that need to be taken care of:
- Provide a capability for Tailwind CSS like config
- Cover all the utilities in Tailwind
- Handle deeply nested partials in Djula templates (at present it can handle only top-level partials in your route templates)

If you find anything that is missing from Tailwind, that can be made part of `cl-djula-tailwind`, please raise an [issue](https://github.com/rajasegar/cl-djula-tailwind/issues) at the repository.

Hope you enjoyed this Common Lisp tooling and the ability to generate CSS util classes on the fly for your web pages. Please let me know for any queries or feedback in the comments section.