Modular JavaScript

Modularity - the ultimate agile tool
Modularity is key to maintainable code. By separating a code base into clear defined modules, we can better isolate code changes. In this way we can more safely make changes to code. It’s easier to understand code if we only need to understand a single module instead of the whole code base.

Modularity, in fact, is not a new concept at all. In the 1960’s there were already papers published about modularity in software development. Modularity has become however more relevant today with the development ofJavaScript. In modern code bases where the application’s UI run entirely in the user’s browser, a significant percentage of the code we write is in JavaScript. At Luminis Technologies we develop large OSGi based applications with JavaScript frontends. The JavaScript code comes close to 50% of the entire code base, while all our backend code is Java. Every large code base needs more work to keep it maintainable, and modularity is one of the key aspects of keeping a large base maintainable.

Basic JavaScript Modularity Pitfalls
To understand module systems in JavaScript we first need to understand the basic mechanics in preventing the most important anti-patterns in JavaScript from leaking globally.

When writing code in JavaScript, everything is defined on a global scope, unless you don’t take extra steps to avoid it. This is a huge problem, because when the code base grows larger it’s impossible to see which code is or isn’t related to each other.

corpo_google_photo_01_nocun_2014_05_15.jpg

The common pattern to prevent this is to use an anonymous function to wrap related code. In the following example, “myLibrary” is defined within an anonymous function which is executed immediately. The “myhelper” function is private (invisible outside the anonymous function), and the “add” method is public. At the end of the example the myLibrary object is pushed on the global scope so that other code can use the API. You shouldn’t force the name of your library on global scope (this should be configurable), but in essence this is what most popular libraries do.

corpo_google_photo_02_nocun_2014_05_15.jpg

This pattern keeps global scope clean, and makes it clearer which code belongs together. It doesn’t, however, solve the problem of dependency management and loading. If we would write our code based only on this pattern, we would have to figure out in which order to load (basically the order of the script tags on the page) our libraries manually, which is painful when working with a large code base or many external dependencies. What we need is a module system to take care of this.

JavaScript Module Systems
When choosing a module systems there are a few requirements that we should keep in mind:

  • The module system should be stable
  • It should be usable in the browser
  • It should take care of dependency managementIt should take care of dependency management
  • Preferably it should be based on a formal specification
With these requirements there are three module systems to look at:
  • Asynchronous Module Definition (AMD)
  • CommonJS
  • ES6 Harmony Modules
AMD is a specification focused on browser based development. It supports asynchronous loading of modules (hence the name), which is important in a browser context. CommonJS comes from the Node.js ecosystem and is focused on server-side development. Although usable in the browser, it lacks asynchronous loading. ES6 is the next JavaScript version, which is currently in the specification process. It’s unclear when the specification will be final, but ES6 will contain a module system as part of the language. This looks really good, but isn’t really useful until browsers support ES6.

Diving into RequireJS
Because we’re specifically looking for a module solution for usage in the browser, AMD is the most powerful choice. The AMD specification has several implementations, the most mature one being RequireJS. At Luminis Technologies we are using RequireJS for all JavaScript projects for quite a while now, and this works out very nicely. Let’s take a look at complete code example based on RequireJS. First of all we replace all our script tags with just a single tag to load RequireJS itself. Also note the data-main property, which defines the name of the RequireJS configuration.

corpo_google_photo_03_nocun_2014_05_15.jpg

This configuration file optionally allows you to map file names to logical names. As a best practice we use this to map versioned file names to unversioned names that we use within our application code. This way we can easily upgrade external libraries. Finally we bootstrap our application by invoking the “require” method. This method takes a list of dependencies that will be loaded, and a function that will be invoked after all dependencies are loaded. Transitive dependencies (in this example a dependency of “mymodule”) will be resolved as well.

corpo_google_photo_04_nocun_2014_05_15.jpg

Finally we have the definition of “mymodule” itself. A module is created by wrapping the code in a “define” function. In most cases an object is returned, which contains the public API of the module (remember the anonymous function example earlier!). The first argument of the define function is an array of dependency names (empty in the example). Dependencies will be loaded before the function is executed.

As you can see it’s quite easy to get started with RequireJS although it solves some difficult issues; defining module, and loading modules including transitive dependency loading.

corpo_google_photo_05_nocun_2014_05_15.jpg

Besides turning our own code into modules and working with libraries that are AMD modules such as jQuery, we might run into libraries that aren’t AMD modules yet. We can use a “shim” configuration to take an existing library that places an object on global scope, and turn it into a module. Our own code can then use the library as if it were a module. The following examples takes AngularJS and makes it usable as a module.

corpo_google_photo_06_nocun_2014_05_15.jpg

The “exports” configuration takes an object from global scope, and makes it available as a module. A shim configuration can also define dependencies. In this case the configuration states that angular depends on jquery (which is already an AMD module), which will make sure that jQuery is loaded before AngularJS.

Other modules can now require angular as if it were an AMD module.

corpo_google_photo_07_nocun_2014_05_15.jpg

Services and Dependency Injection
So far we have been looking at modularity at the level of defining and loading modules. This is an important step, but does not yet cover all aspects of a modular system. As a layer on top of module loading, we need the concept of services. A module is the definition of code and dependencies, while services are about state. AngularJS is a nice example of how services and dependency injection can work in JavaScript, and this can be combined with RequireJS to properly define and load modules at the lower level. The following example shows how an AngularJS service can be used combined with RequireJS. This does look a bit strange, because it looks like we define the same thing twice, but remember that both module mechanisms work on a different level.
If you’re not familiar with Angular, there are some really good tutorials and other resources available online.

First we need to define an AngularJS service. The service definition is wrapped in a RequireJS module that depends on Angular.
corpo_google_photo_08_nocun_2014_05_15.jpg

Next we can define a controller, which again, is wrapped in it’s own RequireJS module and depends on the service module.
corpo_google_photo_09_nocun_2014_05_15.jpg

The Angular application can now be loaded by calling the “bootstrap” Angular method. We do this from our main Require method.
corpo_google_photo_10_nocun_2014_05_15.jpg

Although it takes a little bit more effort to use AngularJS with RequireJS compared to plain AngularJS, we have been doing this on some quite large code bases in the past year. This resulted in a more structured and flexible code base, and I highly recommend it for any non-trivial application.

Towards the Future
As discussed earlier, the ECMAScript 6 specification contains a module system at the language level. Current browsers do not support ECMAScript 6 yet, and it remains unclear when the specification will be final. It is however possible to get started with ES6 code already using the transpiler developers by both Google and Square. These transpilers transform ES6 code to JavaScript that runs in browsers today. However, it is likely that the specification will change before it’s finalized, and this will require code base maintenance. For production applications it might still be a bit early to jump on this train, although it’s definitely interesting to take a look at it.

Alternatively we have found TypeScript to be very useful. TypeScript is a superset of JavaScript (so all JavaScript code is also valid TypeScript code) that adds an (optional) type system, classes, interfaces and a module system. The module system integrates with existing module systems such as AMD. TypeScript compiles to JavaScript and does so very cleanly, so it doesn’t require anything special in the browser. We’ve found that TypeScript offers most of the benefits that ES6 offers, but is available right now. The following example shows how a module can be defined and used in TypeScript, and how this compiles to JavaScript. Notice that this code compiles to RequireJS modules, and look exactly the same as the JavaScript examples we’ve seen earlier!

Defining a Module in TypeScript.
corpo_google_photo_11_nocun_2014_05_15.jpg

corpo_google_photo_12_nocun_2014_05_15.jpg

Using Modules in TypeScript.
corpo_google_photo_13_nocun_2014_05_15.jpg

corpo_google_photo_14_nocun_2014_05_15.jpg

Modularity Front-to-Back
To get an idea of a complete stack using the approaches discussed in this post I will give a short overview of the stack we currently use in our own projects.

All our code is developed in TypeScript, and we generate AMD/RequireJS modules from this code. We use AngularJS for pretty much everything, which works very well in combination with both TypeScript and RequireJS. Our code base is split up in a functional way; the controller code and views are split up per screen. All shared functionality and backend communication is written as Angular services. Each service, controller and directive is packaged in it’s own module. This approach scales well to large code bases and keeps the code base easy to understand.

At the backend we use Java, and all our code is based on OSGi, which is the defacto module runtime in Java. The backend offers RESTful web services and Web Sockets, we don’t use any server side rendering.

This stack gives us modularity front-to-back which has proven to give maintainable and flexible code bases. In a previous LTS webcast I discussed OSGi and modular architecture in the backend. The blog and recording of this webcast can be found here.

corpo_google_photo_15_nocun_2014_05_15.jpg

Comments

Not to be published