Organizing our applications with modules

As our applications scale and grow in size, there will be a time when we will need to better organize our code to make it sustainable and more reusable. Modules are the response for this need, so let's take a look at how they work and how we can implement them in our application. Modules can be either internal or external. In this book, we will mostly focus on external modules, but it is a good idea to overview the two types now.

Internal modules

In a nutshell, internal modules are singleton wrappers containing a range of classes, functions, objects, or variables that are scoped internally, away from the global or outer scope. We can publicly expose the contents of a module by prefixing the keyword export to the element we want to be accessible from the outside, like this:

module Greetings {

    export class Greeting {
        constructor(public name: string) {
            console.log(`Hello ${name}`);
        }
    }

    export class XmasGreeting {
        constructor(public name: string) {
            console.log(`Merry Xmas ${name}`);
        }
    }
}

Our Greetings module contains two classes that will be accessible from outside the module by importing the module and accessing the class we want to use by its name:

import XmasGreeting = Greetings.XmasGreeting;
var xmasGreeting = new XmasGreeting('Joe'); 
// console outputs 'Merry Xmas Joe'

After looking at the preceding code, we can conclude that internal modules are a good way to group and encapsulate elements in a namespace context. We can even split our modules into several files, as long as the module declaration keeps the same name across these files. In order to do so, we will want to reference the different files where we have scattered objects belonging to this module with reference tags:

/// <reference path="greetings/XmasGreeting.ts" />

The major drawback of internal modules though is that in order to put them to work outside the domain of our IDE, we need to have all of them in the same file or application scope. We can include all the generated JavaScript files as script inserts in our web pages, leverage task runners such as Grunt or Gulp for that, or even use the --outFile flag in the TypeScript compiler to have all the .ts files found in your workspace compiled into a single bundle using a bootstrap file with reference tags to all the other modules as the starting point for our compilation:

tsc --outFile app.js module.ts

This will compile all the TypeScript files following the trail of dependent files referenced with reference tags. If we forget to reference any file this way, it will not be included in the final build file, so another option is to enlist all the files containing standalone modules in the compiling command or just add a .txt file containing a comprehensive list of the modules to bundle. Alternatively, we can just use external modules instead.

External modules

External modules are pretty much the solution we need when it comes to building applications designed to grow. Basically, each external module works at a file level, where each file is the module itself and the module name will match the filename without the .js extension. We do not use the module keyword anymore and each member marked with the export prefix will become part of the external module API. The internal module depicted in the previous example would turn into this once conveniently saved in the Greetings.ts file:

    export class Greeting {
        constructor(public name: string) {
            console.log(`Hello ${name}`);
        }
    }

    export class XmasGreeting {
        constructor(public name: string) {
            console.log(`Merry Xmas ${name}`);
        }
    }

Importing this module and using its exported classes would require the following code:

import greetings = require('Greetings');
var XmasGreetings = greetings.XmasGreetings();
var xmasGreetings = new XmasGreetings('Pete');
// console outputs 'Merry Xmas Pete'

Obviously, the require function is not supported by traditional JavaScript, so we need to instruct the compiler about how we want that functionality to be implemented in our target JavaScript files. Fortunately, the TypeScript compiler includes the --module parameter in its API, so we can configure the dependency loader of choice for our project: commonjs for node-style imports, amd for RequireJS-based imports, umd for a loader implementing the Universal Module Definition specification, or system for SystemJS-based imports. We will focus on the SystemJS module loader throughout this book:

tsc --outFile app.js --module commonjs

The resulting file will be properly shimmed, so modules can load dependencies across files using our module loader of choice.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset