A Better Javascript Workflow with Django

Javascript has always been the bane of my existence as a developer. It was the part of the process of developing that I would dread. On the last project I worked on, I found a very simple change that significantly improved my experience writing Javascript.

Problem: One big file

Historically, Javascript lived in one really large file. If you wanted to break up the file, you had to edit your HTML files to make sure they were in the correct order. This always felt really brittle, so I never actually split up my code into multiple files.

Even if you split things up into different files, you had to reply on implicit import mechanisms. A variable would just magically appear in your file because of the import order of the scripts. This incredibly brittle and unintuitive way of working puts up a high barrier to writing well-factored code.

Solution: node-style requires

Node.js has the concept of require. It works very similarly to Python’s import mechanism.

client.js

var events = require('./lib/events')
events.awesome()

lib/events.js

module.exports = {
        awesome: awesome
}

function awesome() {
        console.log("Do awesome stuff.")
}

The module.exports is similar to Python’s __all__, allowing you to explicitly set your public API.

The require system allows you to factor your code into multiple files. More importantly, this allows for code isolation. I can put logic surrounding setting up event handlers in the events.js file, without having it leak into other sections of the code.

Architecting code in this fashion allowed me to write much better code. It gives you an entry point into the code, where the uppermost logic lives. Then you can dive into each specific file to understand that subsection of code. All the benefits normally associated with an import system come to bare.

As a wise man once said:

Namespaces are one honking great idea – let’s do more of those!

Imports in the browser

It’s great that node has an import system, but that doesn’t help me when I’m writing Javascript for the browser. browserify is a project that allows you to have node-style imports in the browser.

Browserify takes all of your Javascript files with imports, and renders them into one large file you can include in your project. It does this by pointing to a file, which is the top-level entry point into your code. In the example above, client.js would be the entry point.

To use Browserify, simply install it with npm:

npm install browserify -g # -g means globally

Then run browserify on your top-level file:

browserify client.js > bundle.js

Browserify outputs the Javascript to stdout, so you can simply redirect it to a file that will contain your bundled Javascript code. The “bundle” is what you include in your HTML:

<script type="text/javascript" src="bundle.js"></script>

A New Problem

As with all preprocessors, the main issue is the workflow around rendering the code into its final form. There are two general approaches for handling this:

  • Have a program watch for file changes, rebuilding on change.

  • Rebuild source files on request.

You can use programs like watchdog and grunt to handle rebuilding of files automatically. The main issue with this is the feedback loop. You can save a file and reload your browser, and you aren’t sure if it’s serving the latest change you made.

I generally prefer having it rebuild the source on request. This works well until you have large files that have to be compiled, where reloading each request introduces significant lag. Luckily for my Javascript projects, they tend towards the smaller side.

Beefy is a project that presents an HTTP server, which autocompiles your Javascript with Browserify. To use beefy you install it:

npm install beefy -g # -g for global install

Django Integration

Beefy also works as a simple HTTP server. It auto-generates your Javascript through Browserify, but also serves normal static media. This means you can point your STATIC_URL at Beefy, and it will just work.

First you have to collect your static media into a single directory:

./manage.py collectstatic

Then, from your STATIC_ROOT you run beefy, pointing at your Browserify entry point:

beefy client.js

You can also pass the bundle you want it to generate with a :. This allows you to point at the same Javascript file in development as in production:

beefy client.js:bundle.js

Beefy should now be serving on port 9966. You can point Django at this for static media by using the setting:

STATIC_URL = 'http://localhost:9966/'

Beefy should now be serving your media properly, and auto-compiling your javascript through Browserify.

Note

You may want to symlink your javascript files from into STATIC_ROOT, if you are doing active development on them.

Conclusion

With this workflow you can now write Javascript with a sane import system, and have it Just Work in development. I hope that it makes the Javascript part of your development a little bit more enjoyable.



Hey there! I'm Eric and I work on communities in the world of software documentation. Feel free to email me if you have comments on this post!