Django Compressor and yUglify

Like many folks out there I’ve been making use of the YUI Compressor for quite a few years as part of my build process (currently as part of Django Compressor). About a year ago it was announced that YUI Compressor was going to go through a deprecation process and in the months since it has been given a new owner and has even seen an update.

But like the YUI team, I figured it’s probably time to move on as there are lots of new tools available including Closure and UglifyJS. The YUI team has also released a new library wrapper around UglifyJS and cssmin with the default YUI configurations on each of them called yUglify. So if you’re coming from YUI Compressor like myself, this library will keep things working basically the same.

To get started, you’ll first need to install yuglify globally via npm (I’m assuming you already have node and npm):

sudo npm -g install yuglify

view raw
yuglify.sh
hosted with ❤ by GitHub

Next you’ll need to update your Django settings with the following:

COMPRESS_YUGLIFY_BINARY = 'yuglify' # assumes yuglify is in your path
COMPRESS_YUGLIFY_CSS_ARGUMENTS = '–terminal'
COMPRESS_YUGLIFY_JS_ARGUMENTS = '–terminal'

view raw
settings.py
hosted with ❤ by GitHub

Now for the new compressor filter:

from compressor.conf import settings
from compressor.filters import CompilerFilter
class YUglifyFilter(CompilerFilter):
command = "{binary} {args}"
def __init__(self, *args, **kwargs):
super(YUglifyFilter, self).__init__(*args, **kwargs)
self.command += ' –type=%s' % self.type
class YUglifyCSSFilter(YUglifyFilter):
type = 'css'
options = (
("binary", settings.COMPRESS_YUGLIFY_BINARY),
("args", settings.COMPRESS_YUGLIFY_CSS_ARGUMENTS),
)
class YUglifyJSFilter(YUglifyFilter):
type = 'js'
options = (
("binary", settings.COMPRESS_YUGLIFY_BINARY),
("args", settings.COMPRESS_YUGLIFY_JS_ARGUMENTS),
)

view raw
filters.py
hosted with ❤ by GitHub

Finally, go back to your Django settings and update your compressor settings with the new filter. Here are some examples (I’ve placed the filter in app/compressor/filters.py):

# Old
COMPRESS_CSS_FILTERS = ['compressor.filters.css_default.CssAbsoluteFilter', 'compressor.filters.yui.YUICSSFilter']
# New
COMPRESS_CSS_FILTERS = ['compressor.filters.css_default.CssAbsoluteFilter', 'app.compressor.filters.YUglifyCSSFilter']
# Old
COMPRESS_JS_FILTERS = ['compressor.filters.jsmin.SlimItFilter']
# New
COMPRESS_JS_FILTERS = ['app.compressor.filters.YUglifyJSFilter']

view raw
settings.py
hosted with ❤ by GitHub

I’ll see if I can push this upstream to the Compressor folks when I’ve got a few minutes.

I’ve also started looking at moving from Django Compressor to Django Pipeline as it supports yUglify out of the box but I’ll save that for another post.

Django Compressor, OfflineGenerationError and 500.html (handler500)

If you’ve tried using Django Compressor in your 500.html error handler you’ve most likely run into an error similar to the following in your logs:

Wed Sep 18 18:04:33 2013] [error] [client 97.115.151.15] OfflineGenerationError: You have offline compression enabled but key "02857b2ef46251c15e3e8648c2ef7a21" is missing from offline manifest. You may need to run "python manage.py compress"., referer: http://www.somesite.com/

view raw
log.txt
hosted with ❤ by GitHub

From the docs… “The default 500 view passes no variables to the 500.html template and is rendered with an empty Context to lessen the chance of additional errors.”

This presents a slight problem if you’re using any type of assets (CSS, images, etc.) in your 500 page as you now have to maintain separate versions just for your 500.html instead of using the ones being generated by Compressor.

The solution is to use a custom class-based view. First, create a new view in your application:

handler500.py

from django.views.generic import TemplateView
class Handler500(TemplateView):
template_name = '500.html'
@classmethod
def as_error_view(cls):
v = cls.as_view()
def view(request):
r = v(request)
r.render()
return r
return view
# must also override this method to ensure the 500 status code is set
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context, status=500)

view raw
handler.py
hosted with ❤ by GitHub

Now tell Django to use it by declaring handler500 in your URLconf:

urls.py

from app.views.handler500 import Handler500
handler500 = Handler500.as_error_view()

view raw
urls.py
hosted with ❤ by GitHub