collectr: Static File Management for All Of Us

collectr: Static File Management for All Of Us

A little while ago I wrote a blog post talking about Git hooks. As an example in that post I wrote a post-commit hook that would minify and upload my static files to S3.

This has spiralled a little bit out of control since then, and I finally drew the line at maintaining that script. It was time to create something more powerful. To this end I give you: collectr.

About collectr

collectr has one goal: to control the management of static files. In its current form it is a wrapper around the boto library. It targets Amazon’s S3 as the back end data store. Its sole purpose in life is to prepare and upload static files to S3 according to the user’s wishes.

In the basic case, where all you want to do is upload some files in a directory to S3, you can keep things simple:

import collectr
collectr.update('path/to/dir', 'bucket_name')

But collectr allows much more than that. In its current early state, collectr supports a few other behaviours: it can minify files, it can ignore files in the directory, and it can apply S3 metadata. A very simple API controls this functionality. For instance, you can apply metadata:

import collectr
directory = collectr.StaticDir('path/to/dir')
directory.metadata = {'Cache-Control': 'max-age=3600'}

You can ignore files:

import collectr
directory = collectr.StaticDir('path/to/dir')
directory.ignore = ['.*\.json']

And you can minify.

Minification

Minification has the most functionality at the moment. To minify files, you want to provide a dictionary. The keys of this dictionary should correspond to the file extensions, and the values correspond to the command-line you want to run to minify the files. This allows collectr to run using whatever minification tool is your favourite (or alternatively, whatever you have installed). For example:

import collectr
directory = collectr.StaticDir('path/to/dir')
directory.minifier = {'js': 'jsmin < {in_name} > {out_name}',
                      'css': 'yuicompressor -o {out_name} {in_name}'}

Each of the files in the directory is checked, and if it has a matching extension, the command-line is executed. It executes in its own shell, so globbing and redirection should work fine. All you need is to remember to put in both an in_name and out_name field in the format string. The out_name is the same as the name of the file, with ‘-min’ appended to it. This will likely become user-configurable in the future.

You might want to keep your pre-minified files in a separate directory. You can do that: call the directory that contains the pre-minified files the input directory like this:

import collectr
directory = collectr.StaticDir('path/to/dir')
directory.input_directory = 'other/dir'
directory.minifier = 'yuicompressor -o {out_name} {in_name}'

The out_file format string variable will move from the first directory to the second, so if you use this feature don’t forget to use out_file!

The above also shows the special-case where the minifier property is a string. If you do this, collectr treats the string as a command-line to be applied to both Javascript and CSS files. This is ideal for using with something like yuicompressor.

You might have noticed that there’s nothing here that specifies minification. This is correct: in fact, you can do any form of pre-processing you like using this method. Enjoy the freedom!

Command-line tools

As a freebie, just to show a simple example of how you can replace your bash scripts with something nicer, collectr comes with a simple script entitled collect_static (hi there Django!). This script is intended to be as close a match as possible to the script I wrote for my post-commit hook. It’s roughly the same length, but the vast majority of it is code for argparse and some if __name__ == '__main__' stuff. In fact, there is only one line of code to handle all of the rest. collectr is really that easy.

The Future

Currently collectr is in version 0.0.4: that is to say, it’s only just started! I’ll be working on this for the near future, and I’d like people’s help as well. If you have an idea for a feature, I want to hear it: even better if you have a Pull Request that implements that feature. I’m looking at supporting other back ends and providing more advanced functionality, so let me know what you want from something like this and I’ll provide it.

Feedback is always appreciated. Let me know what you think, and if you’re using collectr!