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), thentsconfig.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:
compilerOptions
sets the options passed to the compiler during the compilationtarget
determines the JavaScript version of the output JavaScript.es5
is currently a reasonable choice to run the output in multiple runtimes.experimentalDecorators
enables the decorator pattern. This is for future-proofing.rootDir
sets up the base directory of the typescript sources. Usual choices aresrc
or.
(the project root).outDir
sets up the base directory of the typescript output. The usual choices arelib
,dest
,build
, or.
(the project root).sourceMap
toggles the creation of source map files. Useful for debugging purposes as having it enables the stack trace to use the.ts
files’ line location rather than the output.js
files’ line locations.noImplicitAny
marks any parameters that TypeScript infers as having theany
type to be an error condition.strictNullChecks
marks any parameters that could take on thenull
orundefined
values as erroneous conditions.
include
determines which files are included for the compilation pass – it’s an array.exclude
determines which files are excluded for the compilation pass – it’s an array.node_modules
is a good choice to exclude as otherwise they take up unnecessary processing (we are relying on the third-party to have done their due diligence)
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.