Django Template Tags for JavaScript Deferment

I have been slowly working to improve my Django Shared project, which I use as the basis for all my Django projects. Recently, I added several new templatetags for deferring content and scripts: async_script, defer_html, defer_script, and render_deferred_html. Today’s article will cover how to use these templatetags in your own projects.

Getting ready

You will need to include django-shared in your own project, or extract the parts from templatetags/common.py. If you use pip, here is the easiest way to include django-shared:

pip install git+https://github.com/mattsnider/django-shared.git

How do it…

The async_script template tag is a drop-in replacement for the static template tag:

{% static "images/hi.jpg" %}

Becomes:

{% async_script "images/hi.jpg" %}

Except you can also specify absolute urls:

{% async_script "http://www.google-analytics.com/ga.js" 0 %}

The defer_html template tag is a block tag that appends whatever HTML is in the block into a list for future rendering:

{% defer_html %}
Something I want rendered later
{% end_defer_html %}

The defer_script template tag combines defer_html with async_script, so rendering of the script tag is deferred:

{% defer_script "images/test.js" %}
{% defer_script "http://www.google-analytics.com/ga.js" 0 %}

Lastly, render_deferred_html template tag renders the tag blocks that were added by defer_html with async_script:

    …
    {% render_deferred_html %}

</body>
</html>

How it works…

The async_script template tag uses the deferment pattern to append the script to the page. The first argument is required and is the relative path from STATIC_ROOT to your file, or the absolute path to a resource. The second argument is optional (True by default) and indicates that you want to prepend STATIC_ROOT to the first argument; set this to a falsy value, if you use an absolute path instead. This template tag causes a blocking thread to be used to download the content of the script (like normal), but defers the actual parsing and execution of the JavaScript until the UI thread is idle. If you include JavaScript libraries on your page, using this will cause it to render substantially faster, but your scripts will need to be smart enough to run asynchronously. Here is sample output:

<script>(function(d) {
var el = d.createElement('script'),
  elScript = d.getElementsByTagName('script')[0];
    el.type = 'text/javascript';
    el.async = true;
    el.src = 'http://www.google-analytics.com/ga.js';
    elScript.parentNode.insertBefore(el, elScript);
}(document));</script>

The defer_html is a block template tag that appends a global list with the HTML content put in the block. It keeps appending until render_deferred_html is called, which will render all the HTML from the list onto the page and truncate the list. These two tags can be used multiple times on the page, if it solves your business needs, but usually the render_deferred_html is put at the end of the document. These tags currently should not be used in a multi-threaded environment. Since it appends to a global in memory array, you will run into concurrency issues. The Django session id could be used as an identifier, but most people run their python webservers in a multi-process, instead of multi-threaded environment, so I won’ tackle the multi-threaded problem, unless people need it.

In my own projects, I have not found a need for defer_html, but instead use its sister template tag defer_script. The defer_script template tag generates the same HTML and has the same signature as async_script, but appends the script HTML to the global deferment list for rendering with render_deferred_html. I append almost all of my scripts to the document using this deferment combo, so they are rendered at the end of the document and processed asynchronously. After I made these changes, the average loading time for my pages dropped ~50% (as tracked by Google Analytics), which was a substantial improvement.