Django's Asset Managers

django-compressor vs django-webpack-loader

Unlike rails, django doesn't have its own asset pipeline packaged with it. You have to implement one of the community-made extensions.

There are 4 major asset managers for Django: django-compressor, django-pipeline, django-assets, and the newcomer, django-webpack-loader. There are only two I've used in production so far, django-compressor and django-webpack-loader.

Django-compressor

For the past 3 or so large django projects, I have used django-compressor to wrangle together LESS, SASS, and javascript.

The reason I sticked with it for so long is I strove to keep a pure python codebase, without having to rely on making subprocess calls to node programs. Instead of relying on gulp, grunt, and third party node processors, I could rely on a python libsass compiler like django-libsass (which pulls in libsass.)

The other thing I liked about it was being able to declare assets right in the HTML template. To me, this helped my situational awareness a lot better than having the manifest in a separate file, like django-pipeline.

I wasn't really wanting to do a JS heavy website. While I pulled in a SASS-based framework, and maybe a few jquery plugins, but beyond that, I wasn't planning on rewriting everything in react components, for instance.

The other thing is not having to worry about having a secondary process open while developing to build css and js when I did local edits. I thought of CSS/JS as supplemental only.

Limitations and snafus

Local development

First thing, is local files with django-compressor would not remove if they were dependencies of the main files. So if you're SASS files are broken up and pull in stuff via @include, those include files won't update automatically: You have to restart your Django server for that. Ugh.

Problems would arise with libsass bugs: I was excited to try out Primer (GitHub's CSS framework) and found it wouldn't build. As of bootstrap 4, I have to end up enabling COMPRESS_CSS_FILTERS like django-compressor-autoprefixer.

Deployment

compressor seemingly has two sides of it. A friendly, albeit sluggish one when using it for local deveopment. Then, another one, when deploying to production, when nasty compilation errors and missing assets crop up.

The errors when using compressor on production, have been opaque to me. Bugs like:

OfflineGenerationError: You have offline compression enabled but key "50773d66721fbf8659c6e41c3ce6d009" is missing from offline manifest. You may need to run "python manage.py compress".

Even stuff in the documentation, like their behind the scenes page, intended to help, racks my mind. It's a lot of words. Stuff like "nodelist" are jargon that don't make sense to me as a developer who just wanted to compile assets and be on my marry way.

Some will eventually go to file a ticket, and then they'll be closed for not enough information. While it's true maintainers can do little without debug information, django-compressor is notoriously hard and time consuming to create a minimal example of a bug with. And while you're doing it, if you're a django user, you likely have deadlines looming.

webpack w/ django-webpack-loader

Webpack is a few years old. It feels like an intersection between Grunt's declarative-ness and functionality and require.js's config system (with shims and all). Between Webpack and its plugins, it covers what both of them do.

I heard of django-webpack-loader but delayed for a while. Webpack was yet another build system. It seems as if there's a new one every 2 years. First grunt, then gulp, now webpack. And we have to have a separate tab/tmux pane open to watch and rebuild the stuff when we develop.

django-webpack-loader accesses a file called webpack-stats.json. This is not something that comes with webpack by default. It's from webpack-bundle-tracker. It turns out this is a manifest file that django-wepack-loader consumes to spit out webpack files in your templates. OK. So a little adhoc, we're kind of treading on django-pipeline territory, because if we're using webpack, that means we have to keep our own static files in a JS configuration. And also, we're definitely and officially relying on node's SASS and JS compilers now.

Which actually isn't so bad. I feel that node's sass compilation runs circles around python's libsass, in speed and compatibility.

Want to wrap your brain around django + webpack?

I highly recommend Scott Burn's three part tutorial and video.

Inside a webpack + django project

This is probably the thing that kept you from opting to use webpack stuff in the first place, you're going to be swallowing in new libraries merely assisting the compilation process. Let's split things up.

Files being introduced

  • webpack.conf.js - This is the standard file tutorials mention for building with webpack. It can have any file name:

    $ webpack --config ./path/to/webpack.conf.js
    

    You'll be added in BundleTracker to this file:

    var BundleTracker  = require('webpack-bundle-tracker');
    

    See the BundleTracker usage information for that.

  • webpack.base.conf.js + webpack.local.conf.js + webpack.prod.conf.js (or something like that) - You will probably want to break up your JS between environments. For instance, there's no point in running UglifyJS on local files (it adds time to compilation and hinders debuggability). But you do want that on production.

    So a key detail, you'll be using a different BundleTracker path location for each environment, e.g.

    webpack.local.conf.js:

    plugins: [
      new BundleTracker({path: __dirname, filename: './assets/webpack-stats.json'})
    ]
    

    webpack.prod.conf.js:

    plugins: [
      new BundleTracker({path: __dirname, filename: './assets/webpack-stats-prod.json'})
    ]
    

    That means you'll want the django settings for your webpack-bundle-tracker configs to consume a file. Hopefully you're already splitting your django settings files per environment. If not, checkout django-skel's settings for an example of how to do it.

  • webpack-stats.json - this a manifest file generated by webpack-bundle-tracker.

    For mental clarity: This is not an "official" webpack thing, it's a plugin used exclusively by django-webpack-loader.

    You will probably want to ignore the local generation of this, since there's no point in tracking git/svn/mercurial history of stuff that changes this fast.

    You could also name this to webpack-stats-local.json or something to signify it's only for dev purposes.

  • webpack-stats-prod.json (or something similar) - you will probably want to split your webpack config up to handle environments. This is a file you may end up committing alongside your code.

Files to ignore

Ignore the output of bundles from your webpack.conf.js, webpack.conf.*.js, etc. Ignore the webpack-stats.json (or whatever file name you use on your local development server).

To learn about using .gitignore, see https://git-scm.com/docs/gitignore.

Node packages

If you've been staving off adding node modules to build static files, you're going to have to swallow your pride for this one. If you're comfortable will working with node dependencies on your project, however, you'll be pleasantly surprised.

  • webpack: This is the main webpack library. It gives you the webpack command in ./node_modules/.bin/webpack.

  • webpack-bundle-tracker: this is a library you require() (include) in your webpack configuration. It generates the webpack-stats.json (or whatever you decide to name it).

So:

$ npm install --save-dev webpack webpack-bundle-tracker

Python packages

  • django-webpack-loader: consumes webpack-stats.json (or whatever the WEBPACK_LOADER['DEFAULT']['STATS_FILE'] in your DJANGO_SETTINGS_MODULE is.

    It gives you a template extension called render_bundle, load it like this:

    {% load render_bundle from webpack_loader %}
    

    This will load the webpack output (aka bundles)

Only render CSS file

You may notice that webpack produces a JavaScript file when you are using CSS.

If you only want to render the css file, use ExtractTextPlugin. I go into this in greater detail with Extracting CSS files from webpack bundles.

You will notice if you use this plugin, that it also produces a stray JavaScript file. To fix this:

Change {% render_bundle 'bulma' %} to {% render_bundle 'bulma' 'css' %} .