Home > HTML 5, Programming > Care and feeding of large JavaScript projects

Care and feeding of large JavaScript projects

Tetsuo reminds us that projects allowed to grow out of control often become difficult to manage.

When building a webapp in JavaScript, you are using JavaScript in a very different way than it was originally intended when the language was designed.  Case in point for this discussion: most languages designed for big projects provide some sort of mechanism for every source file to describe any other source files it depends upon.  C/C++ has the “#include” directive, Java and C# have a strict class design that allows the compiler to determine where to find every source file, and Ruby has the “require” statement.

The JavaScript language has none of these techniques inherent in the language.  Once you are writing code in a JavaScript file, there is no easy way to pull in other JavaScript files that your current file depends on.  Fortunately, JavaScript developers have come up with several mechanisms to solve this problem.

In the old days before Visual Studio, C/C++ programmers used a tool called a ‘Makefile’ to tell the compiler which source files were needed to build a program (kids, ask your parents).  The Makefile was, as its name implies, a separate file the programmer would need to open and add a new source file to every time a new file was written to add to the project.  It was a cumbersome process and prone to error, but it’s also the easiest pattern for a JavaScript programmer to follow.  If you’re building a web-based application, you can use the index.html page as your “Makefile” and add all JS files you need to the <head> element in your index page to have the browser load all needed files, like so:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>One webapp to rule them all</title>
	<script src="thirdParty/theirResourceA.js"></script>
	<script src="thirdParty/theirResourceB.js"></script>
	<script src="lib/myResourceA.js"></script>
	<script src="lib/myResourceB.js"></script>
	<script src="lib/myResourceC.js"></script>
	<script src="webApp.js"></script>
	<script type="text/javascript">
		var app = new WebApp();
		window.addEventListener('load', function(){app.run();}, false);
	</script>
</head>
<body>
	<div>One does not simply hack into Mordor.</div>
</body>
</html>

There are a few problems with this approach. As your project grows, this list of files will become cumbersome and unwieldy. It will always be a pain for your developers to open the index.html file to add any new files to the architecture, and there’s no easy way to tell which files depend on which other files, which becomes especially onerous if you need to load the files in a specific order to make sure that the files that depend on certain other files are loaded after those files they are dependent upon. Finally, once your webapp is ready for production, you are probably going to want to run all these files through a minifier anyway, at which point you’ll need to edit the index.html file to load only the minified version of the app rather than all the resource files separately.

Don't let this happen to your project.

Fortunately, there are tools like RequireJS which allow us with a minimal amount of restructuring to declare our JavaScript dependencies within each JavaScript file. This makes the dependency tree much clearer which in turn makes it much easier to move a subset of code over to new projects. Developers no longer need to manage a resource list in an external file like the index.html file. And finally, when the project is complete, RequireJS has tools that assist in minifying the entire project for production.

Check out more on RequireJS below the cut

Using RequireJS is primarily a matter of doing two things. First, wrap your modules inside a ‘define’ or ‘require’ function, the top of which is where you’ll describe all the modules this module depends upon. Second, with the exception of a top-level module which is not intended to be used by any other modules (and thus uses the ‘require’ wrapper rather than the ‘define’ wrapper), decide what each module should return as an API for its dependencies to use and return that value at the end of the wrapping function. For example:

lib/dependencyA.js

define(function(){
  return { 
    foo: 10,
    bar: "ten",
    baz: function(){return 10;}
  };
});

lib/dependencyB.js

define(["lib/depencencyA"], function(depA){
  var sum = depA.foo + depA.baz();
  return sum;
});

webapp.js

require(["lib/depencencyB"], function(depB){
  alert("The sum of all dependencies is: " + depB);
});

The sample code shows that at the top of each module, you’re telling RequireJS which other modules you’re dependent upon (if any) after which you provide a callback function that gets called by RequireJS when those modules are loaded and ready. The API populated by each module is handed back to that callback function as a parameter. At the bottom of each module, unless the module is a top-level module wrapped in a ‘require’ call rather than a ‘define’ call, the module specifies its API as the return value. That value is what is passed along as a parameter to the callback functions of any other module depending on it.

RequireJS is extremely flexible in terms of how you define exactly what your module makes available to users of that module. In the example above, ‘dependencyA’ makes an anonymous object available to anyone who requires the module. ‘dependencyB’ uses that anonymous object from dependencyA and simply provides a number (unusual, but allowed) for any other code that would like to use the ‘dependencyB’ module. The top-level webapp code then uses an alert to display the result of the calculations of the dependencies.

So far I’ve been very happy with how easy it has been to migrate my project to using RequireJS as a dependency management system. At first the idea of having each module return an explicit API for itself at the end of the ‘define’ call felt a little awkward, but with a little thinking through, it wasn’t hard to adapt the RequireJS structure to a couple different design patterns I was already using pretty cleanly.

Pattern 1: The standard object module design with RequireJS
If you tend to follow the Java model of having each source file provide the logic for one and only one object, migrating that code to RequireJS is fairly painless. Here’s what I did:

lib/rectangle.js

define(function(){
  // Constructor
  function Rectangle(width, height){
    this.width  = width;
    this.height = height;
  }
  Rectangle.prototype.getWidth() = function(){
    return this.width;
  };
  Rectangle.prototype.getHeight() = function(){
    return this.height;
  };
  Rectangle.prototype.getArea() = function(){
    return this.width * this.height;
  };

  // The magic happens here: return the constructor function for a Rectangle
  // object as the API to the module.
  return Rectangle;
});

webapp.js

// Pull in the Rectangle module
require(["lib/rectangle"], function(Rect){
  // Use the constructor object that got returned by the module to construct
  // a new Rectangle, just as you would with traditional cross-module 
  // JavaScript calls.
  var width   = 10;
  var height  = 20;
  var newRect = new Rect(width, height);
  alert("The area of our rectangle is: " + newRect.getArea());
});

Pattern 2: The singleton object module design with RequireJS
The first pattern can be adapted to singleton objects with a little more code:

lib/system.js

define(function(){
  var singleton = null;

  // Singleton constructor
  function System(){
    if(singleton !== null){
      throw new Error("Cannot instantiate more than one System. System object automatically created in 'require' call for 'lib/system'");
    }
    singleton = this;

    this.SOME_CONSTANT = 0;
  };

  System.prototype.doSomeThing = function(){
  };
  System.prototype.doSomeOtherThing = function(){
  };

  // For the singleton, rather than passing along an object constructor,
  // we construct the singleton object if it doesn't exist yet and pass
  // the singleton object back directly.
  if(!singleton) {
    singleton = new System();
  }
  return singleton;
});

webapp.js

// Pull in the singleton System module
require(["lib/system"], function(system){
  // We don't need to create a new system object since that was handled in
  // the module.
  system.doSomeThing();
  system.doSomeOtherThing();
});

Beyond these two patterns which lend themselves to my personal style of coding, as shown in the first examples, you are free to use whatever other module API mechanism you’d prefer to provide.

One downside of this flexible design is that it requires the code that uses the module to know more about the type of API being returned to it to use. If the caller of the system module above tried to use it in the same way as the rectangle module, or vice versa, unintended behavior would happen. This is A Bad Thing, but arguably the tradeoff of the flexibility of the type of API a module can provide is worth it.

Another side effect of this design is that namespaces are no longer required. In traditional JavaScript design, some architects would hack together nested objects for the sole (but important!) purpose of creating namespaces to avoid potential name collisions, such as, for example, YAHOO.util.Anim for the YUI animation module. With RequireJS, the code that is including the modules is responsible for providing them with the name they will be called within the callback function (via the names of the parameters of the callback function), so there is no longer any need to be concerned about namespace collisions.

I have just begun using RequireJS in my own code, so I may uncover some more learnings in this space as I move forward in my use of the library. If so, I’ll be sure to update the blog with what I learn. For now, I can say that RequireJS seems to be a very solid choice for dependency management and I encourage others to explore it. Good luck in your projects!

About these ads
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: