Signals Blog

Five Tools for Managing Complexity in Large-Scale JavaScript Projects

JavaScript lacks many of the tools and culture that define a clear 'right way' to approach large-scale projects. Yet it's still possible to build and maintain complex software with JavaScript. But how?

For the last year, I've been involved in the development of a chemical structure editor written in JavaScript. ChemWriter is an interactive, graphically-oriented component that combines many characteristics of a drawing program with chemistry domain knowledge.

In the course of developing ChemWriter, we've experimented with many tools and approaches for managing complexity as the capabilities of the software increased. Below is a short list of what we've found essential:

  1. Google Closure. JavaScript offers neither a bundled standard library nor any practical means to split source into simpler units and then efficiently re-assemble them. Closure addresses both of these needs extremely well. The definitive guide by O'Reilly is a must-read.
  2. Test-First Development. If you don't yet test your code first, the single best point I can make is that it greatly reduces time and frustration in debugging sessions.
  3. Jasmine. A good, fast testing environment is essential if you're serious about test-first development. It turns out that JavaScript's dynamic nature and support for closures make it a great fit for BDD, but only with the right tool. Our initial forays into this area lead to Screw.Unit, a slow, buggy tool to say the least that is no longer actively developed. A few months ago, we re-wrote all of our Screw.Unit tests in Jasmine, which has proven itself to be indispensable in its performance and functionality. Jasmine routinely runs over 1,600 automated ChemWriter tests in about one second. This means that every change we make to the ChemWriter source can be validated by running the entire test suite in realtime.
  4. Autospec. Having to re-run tests is no fun. More importantly, it's easy to forget to do. A far better workflow is to write software while having tests run automatically (on a separate monitor) after every file save. Thanks to a little Ruby, I've been using this approach for several months now.
  5. Git Submodules. If you'll be basing your code off of another JavaScript library, you'll quickly find that keeping both codebases in sync to be a major problem. Unlike languages such as Ruby and Python, there is no package manager for JavaScript (at least not the client). Git submodules offer a pretty good solution. For example, we're using major sections of ChemWriter source in ChemTab, a native tablet app under development. Submodules have made it possible to keep both codebases separate, yet cleanly synchronized. Each project that uses Closure includes it as a submodule through the public Closure GitHub repository.

ChemWriter is a component consisting of over 14,000 lines of JavaScript code spread over 112 source files and 14 packages, and is backed by over 1,600 automated tests. Constant experimentation with tools and techniques for managing this codebase has led to a system that works very well for this project and others in progress.