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.
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.
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.
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)
ES6 Harmony Modules
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.
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.
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.
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.
First we need to define an AngularJS service. The service definition is wrapped in a RequireJS module that depends on Angular.
Next we can define a controller, which again, is wrapped in it’s own RequireJS module and depends on the service module.
The Angular application can now be loaded by calling the “bootstrap” Angular method. We do this from our main Require method.
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.
Defining a Module in TypeScript.
Using Modules in TypeScript.
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.
Strona korzysta z plików cookie w celu realizacji usług zgodnie z Polityką prywatności.
Możesz określić warunki przechowywania lub dostępu do cookie w Twojej przeglądarce lub konfiguracji usługi Akceptuj