In this post, we are going to take a look at how to use TailwindCSS 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.
Building Common Lisp web apps with Tailwind CSS
Rajasegar Chandran ・ Jul 6 '21
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.
(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.
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.
(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
<!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:
(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
Now define a new function called render-stylesheet
in src/web.lisp
like below
(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.
(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
(ql:quickload :cl-tw-demo)
(cl-tw-demo:start :port 3000)
Below are some of the sample screenshots of the demo app.
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 and minified using cl-minify-css. The class names from the template files are parsed using 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 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
You can still see some unused CSS with the conventional approach. And using cl-djula-tailwind
in our demo app, this is the result
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.
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
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 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.
Top comments (0)