Universal Jinja: a crazy idea for a Python-ready Frontend

In its early days, Django made a decision to have no opinions about the front-end. At the time, that made a lot of sense. Use whatever you like: it doesn’t matter.

This can make it feel like Django is falling behind. The front-end community has spun up all these interesting ideas, such as rendering UI based off state, and while you can use React or Vue on a Django site, the framework has no official comment or guidance for it.

You can still use them, unless you want to do isomorphic rendering: rendering your components on the server and in the browser using the same code.

And there’s good reasons to want server-side rendering: It’s faster—browsers are really good at rendering plain-old HTML—and works better with search engine crawlers and scraper apps like Instapaper.

None of the javascript isomorphic rendering solutions play well with Django. To get around this, some teams have built a thin Node layer in the front of their app that talks to Django API’s for content and does the server-side rendering in javascript.

I can see the appeal of the that technique if you have a big team. But I’m old-school. I want to fire up a project in an afternoon with a big pot of black coffee and stick the alpha version on a server the next day. Start simple and iterate.

How can we bring a little of that back? How can we do isomorphic rendering with a Python application?

All we would need, at least in theory, is a good template language that both javascript and Python can render.

Rather than write this as a tutorial, I’m going to walk through some of the pieces that make it possible. I’ve done two experiments in what I’m calling Universal Jinja, and you can see the resulting code for yourself:

Jinja2 and Nunjucks

The key is a way to code templates that can render both on the frontend (in javascript) and the backend (in Python).

Nunjucks is a robust javascript templating language that’s heavily inspired by Jinja2, an excellent Python language.

They aren’t quite identical. Nunjucks still has === , and not every tag exists in both libraries, but if you aspire to write dumb templates, and you should, it works very well.

Django-Jinja

Django has finally introduced first-class support for other template languages, notably Jinja2.

If you’re going to use it, use Django-Jinja. It provides a lot of smart helpers like the ability to register a function to the template language using a decorator. Obviously, you can’t use these in javascript without writing and rendering a javascript version, so create new functions sparingly.

I tend to make a Python-only component tag, which is more powerful than Jinja2’s include and can pass context explicitly.

import jinja2 from django.template.loader import render_to_string from django_jinja import library from django.utils.safestring import mark_safe @jinja2.contextfunction @library.global_function def component(global_ctx, template, **context): """ Render a Jinja2 component. Usage: {{ component("my-template.html", title="Foo", modifier="bar") }} """ full_context = dict(global_ctx) full_context.update(context) return mark_safe(render_to_string(template, full_context))

Morphdom

React’s (and before it, Ractive—because credit where its due) premiere trick is to update the HTML in the page automatically based on changes in state, so developers don’t have to manually transverse the DOM and set classes or change elements manually.

This is brilliant, and you don’t need React to do it.

I’ve been using Morphdom (under 10kb minified). You give it two elements: the original, and what the original should transform to match, and it executes the minimum number of steps to make that happen.

In my examples I used it through a lazyRender function that wraps operations in requestAnimationFrame . Then I can take my state, render my app through its Nunjucks template, and pass the string of HTML to this function along with the app’s container element.

function lazyRender(el, html) { let cloneEl = el.cloneNode(); cloneEl.innerHTML = html; window.requestAnimationFrame(function(){ morphdom(el, cloneEl); }); };

Serialization

Django models have a standard obj.get_absolute_url() method, but in the browser, we can’t call Python methods. We’ll want to be able to write <a href="{{ obj.url }}"> , and have render correctly in the browser and on the server.

To solve this, we serialize our data. This converts it to simple data structures: lists, dictionaries, strings, and numbers, which work in Python or as JSON.

I’ve used both Django Rest Framework and Marshmallow to do this. (DRF has a plugin for Marshmallow if that style appeals to you.)

API’s

At this step, we can can render templates in Python or javascript, and automatically update them in the browser based on changes to state.

The final piece is saving data back to the server, and this brings us into much more common territory: Django Rest Framework is the best in class for API’s.

For some interesting thinking on the topic, check out Lightweight Django. It’s a few years old, but I really like the way they talk about these problems.

And this part is up to you

What’s wide open to experimentation is how to actually manage state and handle events. There’s no reason you couldn’t use Backbone or Redux if you wanted, since all I’ve described here is an unopinionated cross-platform view layer.

This is also the place I’ve had most trouble, because the datastore that works best for mutation isn’t always best for rendering in templates.

I ran into this on the Kanban app when moving a card from one column to another. Since cards were children of columns in the datastore, the moved card needs to be popped off its old parent, moved to its new one, each card in both stacks need their positions updated, and all those changes need to be synced to the server.

This is a pain and I’m still not entirely happy with how I solved it.

The point being: this is all new territory, and there are problems to work out that you can’t Google. While I believe there’s something very useful in these techniques, I won’t make wild claims about how this is the future of web development and it will make all your dreams come true.

It works, but should I use it?

Of course, at the end of an experimental project, you have to step back and as, is this actually good? Is it better than the alternatives?

It depends.

If you’re making Slack, you don’t get a lot of benefit from server-side rendering. You might as well render the whole thing in javascript and avoid any extra complexity.

If you’ve got a big team, a distributed front-end where Node communicates with Python API’s might make sense.

On the other hand, one of my favorite web apps these days is Pinboard, which is wicked fast and uses very little javascript at all. That’s not a coincidence. The reason to write fancy frontends isn’t because it’s faster (it’s not), it’s to decouple use actions from pageviews.

But there’s a middle ground, where having some rich components that are both server-rendered and take advantage of state-based-rendering makes a lot of sense. Those apps might be good candidates for techniques like this.

At the very least, I like having the choice.

Feedback and PR’s welcome. If you build something, I’d love to see it.