Session 4 - Converting to TypeScript

Goal

In this session, we are going to try to convert our minimal codebase to TypeScript. We are doing this now to 1) take advantage of what TypeScript has to offer, and 2) to reduce the amount of conversion effort that we would incur if we continue to wait.

What Is TypeScript

In short, TypeScript is a "superset" of JavaScript (this is an explicit design goal, though since JavaScript is also changing, it might be difficult not to have conflicts), where its goal is to add a static type to the JavaScript language.

TypeScript compiles to JavaScript - i.e. it generates readable JavaScript files like how most developers would write the code. So it's a development-time type checker to help developers reduce errors, but it generates JavaScript so it doesn't require any changes to the runtime environments like the browsers or Node.js.

It's not the only one of its kind. Facebook's Flow is meant to do the same thing, but today TypeScript has a larger ecosystem and developers' mind share, so we will make use of TypeScript in this code base here. If you choose to make use of Flow, that's completely fine as well - they are quite similar with minimal differences.

As TypeScript is under very active development, it can potentially obsolete created tutorials and modules quickly, so I will wait until there are active requests before trying to create additional sessions on teaching TypeScript - again, you can help prioritize the work here. Until then, be sure to check on TypeScript's official website for documentations and changes.

Installation

To enable typescript, you just do

npm install --save-dev typescript

Notice that we are using the --save-dev option, which will add it to devDependencies rather than dependencies. This is the right choice TypeScript is development-time tool.

tsconfig.json

After we have installed the typescript compiler, the next thing to do is to create a tsconfig.json, which will help provide the configuration values that the typescript compiler would use.

You can use typescript without tsconfig.json, but if you happen to also use a typescript-aware code editor (like Microsoft Visual Studio Code), then tsconfig.json will also be leveraged by the editor, which makes the development experience a lot better than without.

As always, consult the TypeScript website for the latest documentation. The basic set of tsconfig.json configuration we will start with is as follows:

{
  "compilerOptions": {
    "target": "es5",
    "experimentalDecorators": true,
    "rootDir": ".",
    "outDir": ".",
    "lib": [
      "es5",
      "es2015.promise",
      "dom"
    ],
    "sourceMap": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  },
  "include": [
    "**/*"
  ],
  "exclude": [
    "node_modules"
    
  ]
}

Let's quickly go over the configurations here:

The noImplicitAny and strictNullChecks should be turned on for any new projects to ensure stricter type checking (i.e. helping to catch more errors during development time instead of runtime).

Fixing Type Errors

Once we have created tsconfig.json, the next step is to convert our .js files to .ts files. Since our logic is in index.js - we'll do so by renaming it to index.ts.

You should quickly see the red squiggly showing up in Visual Studio Code (sometimes you might have to restart), as Visual Studio is typescript aware and does the type checking for you real-time, just like word processors do spell checking for your documents real-time.

We'll see, for example, the require function is now highlighted as an error. Since require is a built-in Node.js function, why is it marked as error?

It turns out that TypeScript relies on what's known as type definition files to determine the right types for any code that's originally written in JavaScript. Because it's not practical to convert every single JavaScript code into typescript directly, TypeScript offers a simpler way for the developers of those JavaScript code to offer type checking, and that is a separate TypeScript file.

By default, TypeScript doesn't assume that it runs within the Node.js runtime environment, so there is a Node.js-specific type definition file we have to add to our project in order to type check built-in Node.js modules.

To download a typescript definition file, we utilize the npm in the following pattern:

npm install --save-dev @types/<module you want to type check>

For example, let's say that you want to get the type definition for express, you would do

npm install --save-dev @types/express

Unfortunately, not every single npm modules have a type definition, though if you stick with the popular modules, chances are good that you will find a type definition file for it.

Sometimes the type definition files comes with the module itself, and in that case you don't need to install a separate type definition package.

For Node's type definition, we do the following:

npm install --save-dev @types/node

You'll then see the squigglies under require disappear (might need a restart).

Making use of import

With typescript, it's idiomatic to use import instead of using require, so let's convert over to that.

// var http = require('http'); // javascript version
// ...
import * as http from 'http';
import * as fs from 'fs';
import * as path from 'path';
import * as express from 'express';
import * as MarkdownIt from 'markdown-it';

Once we do that we can see that there are a couple of packages that we don't have type definitions for yet (express and markdown-it), so let's install them as well.

npm install --save-dev @types/express @types/markdown-it

The squigglies would disappear afterwards.

With all the type definitions installed, it's time to fix the rest of errors, which basically comes from us having the noImplicitAny option enabled.

For example, all of our req parameters should now take on the type of express.Request, and res takes the type of express.Response, and the next parameter is express.NextFunction. So it looks like:

function (req : express.Request, res : express.Response, next : express.NextFunction) {
  // ...
}

The conversion is mostly mechanical, so we won't repeat for all of the different types that exist in the existing index.js here.

Once you try to convert - if you use a typescript-aware editor like Visual Studio Code, you can see the errors start to disappear in the status bar. If not, you'll need to run the compiler to see the changes.

Let's go ahead and run the compiler.

./node_modules/.bin/tsc --watch

The --watch argument will start incremental compilation, so anytime you make a change and save, you'll see tsc to try to compile again and give you a new set of errors.

Once you have removed all of the errors - you have successfully converted the JavaScript code over to TypeScript! Restart the server to test what we did in Session 3 didn't break.

Conclusion

Using a static type checker for JavaScript like TypeScript (or if you prefer, Flow) helps with catching errors earlier and in automated fashion, which will reduce your development effort. By converting now, we start early so it's an easier process.

We are now ready to add more complexity to our code with confidence in future sessions.