Chapter 7. Modularization

Modularization is a popular technique used in modern programming languages that allows programs to be built from a series of smaller programs, or modules. Writing programs that use modules encourages programmers to write code that conforms to the design principle called "Separation of Concerns". In other words, each module focuses on doing one thing, and has a clearly defined interface. If we then consume this module by focusing on the interface, we can easily replace this interface with something else, without breaking our code. We will focus more on "Separation of Concerns" and other object-oriented design patterns in the next chapter.

JavaScript, in itself, does not have a concept of modules, but it is proposed for the upcoming ECMAScript 6 standard. Popular frameworks and libraries such as Node and Require have built module-loading capabilities into their frameworks. These frameworks, however, use slightly different syntax. Node uses the CommonJS syntax for module loading, whereas Require uses the Asynchronous Module Loading (AMD) syntax. The TypeScript compiler has an option to turn on module compilation, and then switch between these two syntax styles.

In this chapter, we will look at the syntax of both module styles, and how the TypeScript compiler implements them. We will take a look at how to use modules when writing code for both Node and Require. We will also have a cursory look at Backbone, and how to write an application using a Model, View and Controller. Each of these Backbone components will be built as loadable modules.

CommonJs

The most prevalent usage of the CommonJs syntax for writing modules is when writing server-side code. It has been argued that browser-based CommonJs syntax simply cannot be done without a lot of overhead, but there are some libraries out there such as Curl (https://github.com/cujojs/curl) that allow this syntax. In this section, we will, however, focus on Node application development.

Setting up Node in Visual Studio

Using Node within Visual Studio has been made a breeze by the Node tools for Visual Studio plugin (https://nodejstools.codeplex.com). This toolset has also been updated to use TypeScript as a default editor, bringing the full TypeScript development experience to Node. Once the extension has been installed, we can create a new blank Node application, as shown in the following screenshot:

Setting up Node in Visual Studio

Creating a blank Node application with the Node toolset

This project template will create a server.ts TypeScript file, and include the node.d.ts declaration file automatically for us. If we compile and run this default implementation by simply hitting F5, the project template will automatically start up a new console to run our Node server, start the server instance, and open a browser to connect to this instance. If all goes well at this stage, your browser will simply say Hello World.

Let's take a look at the server.ts TypeScript file that is creating an instance of our Node server:

import _http = require('http'),
var port = process.env.port || 1337
http.createServer(function (req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello World
'),
}).listen(port);

The first line of this code snippet uses the CommonJs module syntax to tell our Node server that it must import the library named 'http'.

This line has two key parts. To explain these key parts, let's start at the right-hand side of the = sign and work our way towards the left. The require function takes a single parameter, and is used to tell the application that there is a library named 'http' out there. The require function also tells the application that it needs this library to be made available to it, in order to continue functioning. As require is a key part of the syntax of modules in TypeScript, it has been given the keyword status and will be highlighted in blue, just like other keywords such as var, string, and function. If the application cannot find this 'http' library, then Node will immediately throw an exception.

The left-hand side of the = sign uses the import keyword, which is also a fundamental concept in module syntax. The import statement tells the application to attach the library that has been loaded via the require function, require('http'), into a namespace called _http. Whatever functions or objects that the 'http' library has made public will be available to the program via the _http namespace.

If we jump to the third line very quickly, we can see that we invoke a function called createServer that is defined in the 'http' module, and call it via the _http namespace. hence _http.createServer().

Note

The default server.ts file that is generated by the blank Node project template is very slightly different than our preceding code sample. It names the import http, which matches the library name 'http', as follows:

import http = require('http'),

This is a common naming standard for Node. You can, of course, name your import namespaces whatever you like, but it does help to have the namespace match the imported library's name, to help with the readability of the code.

The second line of our code snippet simply sets up the variable named port to either be the value of the global variable process.env.port, or a default value of 1337. This port number is used on the very last line, and uses fluent syntax to call the listen function on the returned value of the http.createServer function.

Our createServer function has two variables named req and res. If we use our mouse to hover over the req variable, we can see that it is of type _http.ServerRequest. Similarly, the res variable is of type _http.ServerResponse. These two variables are our HTTP request and response streams. In the body of the code, we are invoking the writeHead function on the HTTP response to set the content-type, and then we are invoking the end function on the HTTP response to write the text 'Hello World ' to the browser.

With these couple of lines of code, we have created a running node HTTP server that serves up a simple web page with the text "Hello World".

Note that if you have a keen eye for TypeScript syntax, you will notice that this file uses JavaScript syntax and not TypeScript syntax for our createServer function. This is most probably due to the recent upgrade of the Node toolset from JavaScript to TypeScript. The call to createServer can also be written using TypeScript fat arrow syntax as follows:

_http.createServer((req, res) => { .. }

Creating a Node module

To create a Node module, we simply need to create another TypeScript file to house our module code. Let's create a file named ServerMain.ts, and move the code that writes to the HTTP response into this module as follows:

import http = require('http'),
export function processRequest(
    req: http.ServerRequest,
    res: http.ServerResponse): void
{
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello World
'),
}

Our ServerMain module starts with the import of the 'http' module into the http namespace. This is necessary to allow us to use the ServerRequest and ServerResponse types that are part of this library.

The keyword export is now used to indicate what functions will be made available to users of this module. As we can see, we have exported a function named processRequest that takes two parameters, req and res. This function will be used as a replacement for the anonymous function (req, res) => { … } that we were using in the server.ts file previously.

Note that as good TypeScript coders, we have also strongly typed the req and res variables to be of type http.ServerRequest, and of type http.ServerResponse respectively. This will enable Intellisense within our IDE, and also adheres to two principles of strong typing (S.F.I.A.T and self-describing functions).

Before we modify the server.ts file to use our new module, let's crack open the generated JavaScript file and take a closer look at the CommonJs syntax in a little more detail:

function processRequest(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello World
'),
}
exports.processRequest = processRequest;

The first part of this JavaScript is simple enough—we have a function named processRequest. The last line, however, attaches this function to a property on the exports global variable. This exports global variable is how CommonJs publishes modules to the outside world. Any function, class, or property that needs to be exposed to the outside world must be attached to the exports global variable. The TypeScript compiler will generate this line of code for us whenever we use the exports keyword in a TypeScript file.

Using a Node module

Now that we have our module in place, we can modify our server.ts file to use this module as follows:

import http = require('http'),
import ServerMain = require('./ServerMain'),
var port = process.env.port || 1337;
http.createServer(ServerMain.processRequest).listen(port);

The first line stays the same, but the second line uses the same import and require syntax to now import our './ServerMain' module into the ServerMain namespace.

Note

The syntax that we use to name this module points to a local file module, and therefore uses a relative file path to the module file. This relative path will resolve to the ServerMain.js file that TypeScript has generated. Creating a global Node module with the name 'ServerMain', which would be globally available—similar to the 'http' module—is outside the scope of this discussion.

Our call to the http.createServer function now passes in our processRequest function as an argument. We have changed from an anonymous function using the fat arrow syntax, to a named function from the ServerMain module. We have also started to adhere to our "Separation of Concerns" design pattern. The server.ts file starts the server on a specific port, and the ServerMain.ts file now houses the code used to process a single request.

Chaining asynchronous functions

When writing Node code, it is necessary to take a careful note of the asynchronous nature of all Node programming, as well as JavaScript's lexical scoping rules. Luckily, the TypeScript compiler will generate errors if we break any of these rules. As an example of this, let's update our ServerMain module to read in a file from disk, and serve up the contents of this file, instead of our Hello world text, as follows:

import fs = require("fs");
export function processRequestReadFromFileAnonymous(
      req: http.ServerRequest, res: http.ServerResponse) 
{
    fs.readFile('server.js', 'utf8', (err, data) => {
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        if (err)
            res.write("could not open file for reading");
        else {
            res.write(data);
            res.end();
        }
    });
}

To read files from disk, we will need to use the Node global module named "fs", or filesystem, which is imported on the first line of the code. We then expose a new function named processRequestReadFromFileAnonymous that again uses the req and res parameters. Within this function, we then use the fs.readFile function to read a file from disk using three arguments. The first argument is the name of the file to be read in, the second argument is the file type, and the third argument is a callback function that Node will call, once the file has been read from disk.

The body of this anonymous function is similar to what we have seen previously, but it also checks the err argument to see whether there was an error while loading the file. If there was no error, the function simply writes the file to the response stream.

In real-world applications, the logic inside of the main processRequestReadFromFileAnonymous function could become quite complex (besides the name), and may involve more than a single step to read a hardcoded filename from disk. Let's move this anonymous function into a private function, and see what happens. Our first pass at refactoring this code may be something similar to the following:

export function processRequestReadFromFileError(
    req: http.ServerRequest, res: http.ServerResponse) 
{
    fs.readFile('server.js', 'utf8', writeFileToStreamError);
}
function writeFileToStreamError(err, data) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    if (err)
        res.write("could not open file for reading");
    else {
        res.write(data);
        res.end();
    }
}

Here, we have modified the fs.readFile function call, and replaced the anonymous callback function with a named function—writeFileToStreamError. This change, however, will immediately generate a compilation error:

Cannot find name 'res'.

This compilation error is caused by the lexical scoping rules of JavaScript. The function writeFileToStreamError is trying to use the res parameter of the parent function. However, as soon as we moved this function outside the lexical scope of the parent, the variable res is no longer in scope – and will therefore be undefined. To fix this error, we need to ensure that the lexical scope of the res argument is maintained within our code structure, and we need to pass the value of the res argument down to our writeFileToStream function, as follows:

export function processRequestReadFromFileChained(
    req: http.ServerRequest, res: http.ServerResponse) 
{
    fs.readFile('server.js', 'utf8', (err, data) => {
        writeFileToStream(err, data, res);
    });
}
function writeFileToStream(
    err: ErrnoException, data: any, 
    res: http.ServerResponse): void 
{
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    if (err)
        res.write("could not open file for reading");
    else {
        res.write(data);
        res.end();
    }
}

Note that in the call to fs.readFile on the third line in the preceding code, we have reverted back to our anonymous syntax, and passed on the value of the parent res argument down to our new function writeFileToStream. This modification of our code now correctly adheres to the lexical scoping rules of JavaScript. Another side-effect is that we have clearly defined what variables the writeFileToStream function needs, in order to work. It needs the err and data variables from the fs.readFile callback, but it also needs the res variable from the original HTTP request.

Note

We have not exported the writeFileToStream function; it is purely an internal function for use within our module.

We can now modify our server.ts file to use our new chained function:

http.createServer(ServerMain.processRequestReadFromFileChained) .listen(port);

Running the application now will show the world what our server.js file contains:

Chaining asynchronous functions

The Node application serving the contents of a file on disk

Note that because we are using modules, we have been able to write three different versions of the processRequest function, each with a slight twist. However, our modifications to the server.ts file that launches the server have been very simple. We have just replaced the function that the server calls, in order to effectively run three different versions of our application. Again, this complies with the "Separation of Concerns" design principle. The server.ts code is simply used to start the Node server on a specific port, and should not be concerned with how each request is processed. Our code within ServerMain.ts is responsible simply for processing a request.

This concludes our section on writing Node applications within TypeScript. As we have seen, the TypeScript developer experience brings with it a compilation step, which will quickly trap lexical scoping rules and many other issues within our code. Final score, TypeScript: 1, buggy code: 0!

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

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