Building CSS/JS static assets and Django

by Sebastien Mirolo on Thu, 4 Jul 2019

In the early days of the Internet, you added your .css and .js files in a static/ folder under your Django project and you were done. Things have evolved. Getting the User Interface static assets to a browser is a lot more complex now.

Static assets are files that do not need to be generated on a per-request basis. These typically include media files (images and videos), css files and JavaScript files. They are static in the sense that every user will receive the same copy, and as a result they can be cached by a Content Delivery Network (CDN).

In the early days of the Internet, it was very common to edit plain .css and .js files. As a result, you only had to add your .css and .js files in a static/ folder under your Django project and you were done. Nowadays, a build pipeline typically looks like this:

Extend CSS to simplify updates

The original CSS specification lacked variables. This became a shortcoming as the size and complexity of .css files soared. It was difficult to maintain consistency in look-and-feel since multiple places had to be updated to change a theme color for example. Less and SCSS are competing approaches to improve on the shortcomings of CSS.

Though Bootstrap v3 used .less files, Bootstrap v4 was re-written to use scss syntax. Many popular CSS frameworks are now using SCSS syntax.

CSS FrameworkLessSCSS
BulmaYes
Bootstrap v4Yes
FoundationYes
MaterializeYes
MetroUIYes
Semantic UIYes
UIKitYesYes

Optimize CSS

CSS Frameworks have been a great help to quickly build beautiful responsive websites. Unfortunately the more bells and whistles the larger the resulting .css assets are. That is a problem when page load time is paramount to retain users on a site.

A tool like PurgeCSS will go through your HTML templates, mark CSS that is used and unused, then remove unused CSS from the final .css asset file.

Translate Javascript code for older browsers

The Javascript language is evolving very rapidly but browser clients are not updated as quickly. In many ways you will write code that needs to be polyfilled and/or tranpiled to run in production. That is what tools like Babel do.

Even though the early Javascript workflows were an edit/run cycle, nowadays Javascript development workflows are much closer to compiled languages development (like C, C++ or Go) in the sense that they require an edit/compile/link/run cycle.

Create international language maps

You could write a web product fully in English, Spanish, Chinese or any language your initial target market is but ultimately you will have to support multiple local languages through internationalization (i18n). That means:

  • Extract strings that should be translated from the source code
  • Have a person translate each string into the target language
  • Build a Javascript file out of the translation units (.po file)

Minify and compress css and js files

Typically whitespaces are irrelevant and only added to a source file to help humans read them. By removing whitespaces, file sizes can be significantly reduced, hence helping with page load time.

Note that whitespace is sometimes relevant in CSS. For example, the following two lines are not equivalent.

width: calc(100vh - 20px);
width: calc(100vh-20px);

Most CSS minifiers have stumbled upon that issue (ex: node-cssmin, YUI compressor#59).

Browsers accept content that is compressed with gzip, or brotli for modern ones, and transparently decompress it before using it to render a web page.

The browser will typically send an HTTP header along with the request:

Accept-Encoding: gzip, deflate

The server will then reply with a (.gz) compressed file and an HTTP header:

Content-Encoding: gzip

Keep CSS/JS in sync with HTML

Ultimately the .css or .js assets will be linked through an URL in an HTML page. If we were to never update the .css and .js after the first version is sent to production, it would be trivial. It is not the case though. We are making changes and updating a CDN cache takes time (not to mention many components along the way from the server to the client browse have cache-related bugs). The most common approach to deliver an HTML page and the associated CSS/JS assets reliably is thus to create unique file names (ex: site-hash.css). Effectively we deploy each asset file to production only once.

The problem thus shifts into modifying the URL link into the HTML page. Typically when you are using Django, than means using special templatetags. For examples:

# django-assets
{% assets "css_base" %}
<link rel="stylesheet" media="screen" href="{{ ASSET_URL }}" />
{% endassets %}

# django-webpack-loader
{% render_bundle 'main' 'css' %}

We have been using django-assets for a long time but it seems obvious as we move towards a front-end heavy architecture that using webpack opens more future opportunities.

Side Note: You might also want to use Parcel or Gulp. These also have Django integration. Once you think about Glup, straight Makefiles also work very well.

Integrate Webpack with Django

Once you setup the few hooks necessary to generate URLs to asset bundles in Django templates, most of the configuration of the build pipeline is done in webpack.config.js.

by Sebastien Mirolo on Thu, 4 Jul 2019


Bring fully-featured SaaS products to production faster.

Follow us on