Using SVGs in Common Lisp web apps with Djula
Tags: commonlisp, lisp, svg, webdev
Published: 2022-08-08 07:12:26

In this post, we are going to take a look at how to use SVG content or embed SVGs inside your HTML with the [Djula](https://github.com/mmontone/djula) templating engine in a Common Lisp web application.

Before diving into the topic, let's understand some tools and technologies we will be needing for the post. This way we can easily understand the development workflow and the challenges that comes with embedding SVGs in our Common Lisp web applications.

## Djula
Djula is a port of Python's Django template engine to Common Lisp. It's the default templating engine used by the framework [Caveman](https://github.com/fukamachi/caveman) for building web applications


## SVG
Scalable Vector Graphics is an XML-based vector image format for defining two-dimensional graphics, having support for interactivity and animation. The SVG specification is an open standard developed by the World Wide Web Consortium since 1999.

SVGs can look crisp at all screen resolutions, can have super small file sizes, and can be easily edited and modified.

Let's take a look at a sample SVG document copied from [Bootstrap Icons](https://icons.getbootstrap.com/icons/airplane-engines-fill/) called `airplane-engines-fill`

```html
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-airplane-engines-fill" viewBox="0 0 16 16">
  <path d="M8 0c-.787 0-1.292.592-1.572 1.151A4.347 4.347 0 0 0 6 3v3.691l-2 1V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.191l-1.17.585A1.5 1.5 0 0 0 0 10.618V12a.5.5 0 0 0 .582.493l1.631-.272.313.937a.5.5 0 0 0 .948 0l.405-1.214 2.21-.369.375 2.253-1.318 1.318A.5.5 0 0 0 5.5 16h5a.5.5 0 0 0 .354-.854l-1.318-1.318.375-2.253 2.21.369.405 1.214a.5.5 0 0 0 .948 0l.313-.937 1.63.272A.5.5 0 0 0 16 12v-1.382a1.5 1.5 0 0 0-.83-1.342L14 8.691V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v.191l-2-1V3c0-.568-.14-1.271-.428-1.849C9.292.591 8.787 0 8 0Z"/>
</svg>
```

which will actually look like this:


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

## Creating our demo project
Let's bootstrap a new project using Caveman to showcase embedding SVGs in our web pages.

```lisp
(ql:quickload :caveman2)
(caveman2:make-project #P"~/quicklisp/local-projects/cl-svg-demo")
```

For more detailed information of setting up and using Caveman framework you can check out my previous post

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

## Using SVGs in Djula templates

Embedding SVG content inside Djula templates is actually straightforward. We are going to make use of the built-in tag in Djula called `include` to embed our SVG file.

The `include` tag loads a template and renders it with the current context. This is a way of “including” other templates within a template.

This example includes the contents of the template "foo/bar.html":

```html
{% include "foo/bar.html" %}
```

A set of parameters can also be added, which become available as context variables when the included template is rendered:

```html
{% include "user.html" :user record.creator %}
{% include "user.html" :user record.updater %}
```

Let's say we want to embed the `airplane-engines-fill` svg. First create a folder called `svgs` inside the `templates` folder in your Caveman application. The reason for creating a new folder for SVGs inside the existing `templates` folder is to have a standard convention for putting all our SVG files in one place and also it will be easy to refer them in our templates using the relative path like `svgs/my-icon.svg`.

Next, create a new file inside the newly created folder called `airplane-engines-fill.html` and copy the svg content shown above.

```
templates/svgs/airplane-engines-fill.html
```

So, if we make use of the `include` tag to embed SVG files in our templates, let's open a template `templates/index.html` and add the following code.

```html
{% include "svgs/airplane-engines-fill.html" %}
```

Voila, that's it, if you check your page, our svg is now rendered in the page. Now I want to customize the SVG rendering, let's say, I want it bigger, since the default height and width of the SVG is just `16px` as mentioned in the file.

So you can go and update your SVG file and change the width and height attributes or you can take a copy of the file and make the changes and use the new file. But this is a tedious job, you don't know beforehand how many dimensions you need to support. So the best option here is to override the attributes within your templates and don't touch the original source file for any changes.

You can open the SVG file and add some Djula conditional tags like `{% if %}` and `{% else %}` to make the customizations. In our case it will be something like, to override the `height` attribute

```html
height="{% if height %}{{height}}{% else %}16{% endif %}" 
```

What this tells us is that, if there is a height value supplied to this template (all things become a template if you use the `include` tag in Djula), then that value is take otherwise the default height value of `16` is used.

And now in our template we can supply the same values like

```html
{% include "svgs/airplane-engines-fill.html"  :height 64 %}
```

### Bonus tip: Need not to rename SVG files
One surprising thing I found out while working on this demo is that, actually you  don't need to rename your SVG files to `.html` extension. You can just use something like:

```html
{% include "svgs/airplane-engines-fill.svg"  :height 64 %}
```

This is because, Djula can understand proper markup content inside your files (templates which you want to embed in other templates), be it whatever extension you use, as long as the content is a properly formatted XML/HTML markup and SVGs are just perfect.

In similar way we can customize the attributes of the root element `svg` in our template like below. We have customized the height, width, fill  and class attributes.

```html
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="{% if class %}{{class}}{% else %}bi bi-airplane-engines-fill{% endif %}" 
fill="{% if fill %}{{fill}}{% else %}currentColor{% endif %}" 
height="{% if height %}{{height}}{% else %}16{% endif %}" 
width="{% if width %}{{width}}{% else %}16{% endif %}">
<path xmlns="http://www.w3.org/2000/svg" d="M8 0c-.787 0-1.292.592-1.572 1.151A4.347 4.347 0 0 0 6 3v3.691l-2 1V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.191l-1.17.585A1.5 1.5 0 0 0 0 10.618V12a.5.5 0 0 0 .582.493l1.631-.272.313.937a.5.5 0 0 0 .948 0l.405-1.214 2.21-.369.375 2.253-1.318 1.318A.5.5 0 0 0 5.5 16h5a.5.5 0 0 0 .354-.854l-1.318-1.318.375-2.253 2.21.369.405 1.214a.5.5 0 0 0 .948 0l.313-.937 1.63.272A.5.5 0 0 0 16 12v-1.382a1.5 1.5 0 0 0-.83-1.342L14 8.691V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v.191l-2-1V3c0-.568-.14-1.271-.428-1.849C9.292.591 8.787 0 8 0Z"/></svg>
```

But doing this for multiple SVG files is a time consuming process and is error-prone if we do it manually. And also you want to keep your original SVG files untouched for any other modification if the Designers in your team wanted to improve it or give a replacement for the icons or images.

So, ideally you want two versions of your SVG files, one is the original untouched and un-optimized files which doesn't include any override logic and the other one is the modified version which is we use it in our templates.

To solve these problems and have a better development workflow, I have created a package called `cl-djula-svg` which automates all the above steps for you. The inspiration for this package came from [svgr](https://react-svgr.com/) which exactly does the same thing in [React](https://reactjs.org) applications. It takes a SVG file, process some override logic, optimize it and create React wrapper components based on the SVG content.

## cl-djula-svg
A Common Lisp package that will help process SVG files and copy them to folders so that they can be used in Djula templates in a Caveman web application framework.

### Installation

You can install cl-djula-svg via quicklisp once it becomes available (at present, it is not, you have clone the repo manually):

```lisp
(ql:quickload :cl-djula-svg)
```

The tracking issue for adding cl-djula-svg to quicklisp is [here](https://github.com/quicklisp/quicklisp-projects/issues/2196). You may want to check it for any updates.

### Usage
Please ensure you keep your original SVG files in a folder `static/svgs` and it will be copied to `templates/svgs`

You can use the `copy-svg` function from this package by supplying the source and destination directories:

You can add the below code to your `src/web.lisp` before your route definitions.

```lisp
(cl-djula-svg:copy-svg (merge-pathnames #P"static/svgs/" *application-root*) (merge-pathnames #P"templates/svgs/" *application-root*))
```

This is it, so everytime before your server is started, cl-djula-svg ensures that all the SVG files are processed by adding the necessary override logic for the attributes  and copy it to the destination directory `templates/svgs`.

And in the template, `templates/index.html` you can embed and customize your SVG content like below:

```html
	<h2>width and height</h2>
	<div class="flex">
		<div>
			{% include "svgs/airplanes-engine-fill.svg" :width 128 :height 128 %}
		</div>
		<div>
			{% include "svgs/airplanes-engine-fill.svg" :width 64 :height 64 %}
		</div>
		<div>
			{% include "svgs/airplanes-engine-fill.svg" :width 32 :height 32 %}
		</div>
	</div>
	<h2>fill</h2>
	<div class="flex">
		<div>
			{% include "svgs/airplanes-engine-fill.svg" :width 128 :height 128 :fill "purple" %}
		</div>
		<div style="color:green">
			{% include "svgs/airplanes-engine-fill.svg" :width 64 :height 64 %}
		</div>
		<div style="color:blue">
			{% include "svgs/airplanes-engine-fill.svg" :width 32 :height 32 %}
		</div>
		</div>
	<h2>class</h2>
			<div>
			{% include "svgs/airplanes-engine-fill.svg" :width 128 :height 128 :class "fill-red" %}
		</div>
	</div>
```


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

## What's next
There are still a lot of things `cl-djula-svg` is capable of doing. For the immediate future, I am looking at adding optimization capabilities something like what [svgo](https://github.com/svg/svgo) is doing for `svgr`. If you know anything else needs to be done to improve the package, please open an issue in the [repository](https://github.com/rajasegar/cl-djula-svg).

## Source Code
The source code for `cl-djula-svg` is  hosted in [Github](https://github.com/rajasegar/cl-djula-svg) and the demo project is [here](https://github.com/rajasegar/cl-svg-demo)


## References
- [Djula](https://github.com/mmontone/djula)
- [Caveman](https://github.com/fukamachi/caveman)
- [SVG on the web - A Practical guide](https://svgontheweb.com/)
- [svgr](https://react-svgr.com/)

Hope you enjoyed the post and start using SVGs in your Common Lisp web applications to get better images resolutions, smaller file sizes and better performance with Djula and Caveman. Please let me know your queries and feedback in the comments section.