Migrating to TypeScript
Tags: typescript, tutorial, javascript, beginners
Published: 2019-10-27 05:52:20

---
title: Migrating to TypeScript
published: true
date: 2019-10-27 05:31:45 UTC
tags: TypeScript,Tutorial,JavaScript,Beginners
series: Quality JavaScript with TypeScript
cover_image: https://killalldefects.com/wp-content/uploads/2019/10/JStoTS.png
canonical_url: https://killalldefects.com/2019/10/27/migrating-javascript-to-typescript/
---

In this article, we’ll discuss [TypeScript](https://www.typescriptlang.org/), its benefits, and how to introduce it to a legacy piece of JavaScript code.

By the end this article you’ll learn:

- What TypeScript is and what its benefits and tradeoffs are
- How to get started with TypeScript with a legacy JavaScript codebase
- How to use type annotations in TypeScript
- How to use nullability checks in TypeScript
- Next steps for further improving TypeScript code

## What is TypeScript?

So, what is TypeScript and why should you use it?

Put simply, TypeScript is a superset of JavaScript. Think of it as JavaScript with additional annotations and static type checking.

TypeScript _transpiles_ down to JavaScript, so any browser that runs JavaScript can run code written in TypeScript. TypeScript can also target older versions of JavaScript. This lets you use modern JavaScript features like classes, arrow functions, `let/const`, and template strings while targeting browsers that don’t yet support these things.

Additionally, TypeScript’s static checking makes entire classes of defects impossible, which is [something I feel very strongly about](https://killalldefects.com/2019/08/29/making-defects-impossible/).

With that brief introduction, let’s meet the app we’ll be migrating to TypeScript.

## The Sample Application

We’ll be working with a simple JavaScript application that we’ll migrate to TypeScript.

The code is available on GitHub in its [initial JavaScript state](https://github.com/IntegerMan/MigratingToTypeScript/tree/migrateStart) (with a few bugs) and its [finished TypeScript state](https://github.com/IntegerMan/MigratingToTypeScript/tree/migrateEnd). If you’d like to play with the final fixed version in your browser, it is [available online](https://integerman.github.io/MigratingToTypeScript/).

The app is a simple test case manager where the user types in the name of a test case and adds it to the list. Test cases can then be marked as passed, failed, or deleted.

![The sample application, showing a list of test cases](https://i2.wp.com/killalldefects.com/wp-content/uploads/2019/10/image-24.png?w=770&ssl=1)

This is an intentionally simple and intentionally buggy app. It doesn’t use any JavaScript frameworks or even any JavaScript libraries – not even JQuery or Underscore / Lodash.

The app does use [Bootstrap v4](https://getbootstrap.com/) with [Bootswatch’s Darkly theme](https://bootswatch.com/darkly/) in order to keep the HTML simple with a clean UI for this article.

### Existing HTML

While our focus is going to be on the JavaScript, there are a few things in the HTML to be aware of:

{% gist https://gist.github.com/IntegerMan/28cad932e91171659f35a13b84d6fbc3 %}

Specifically, let’s look at a few lines:

- Line 7 imports our main JavaScript code
- Line 22 references `addTestCase` defined in our JavaScript code.
- Line 27 – `lblNoTestCases` is a label that is shown if no test cases exist
- Line 28 – `listTestCases` is a placeholder for the test case UI elements

### Startup JavaScript Code

With that aside, let’s look at the existing code in a few chunks:

{% gist https://gist.github.com/IntegerMan/2c944c95969adca62edc74d96487072a %}

Here we define a `TestCase` class that serves as our primary (and only) entity in this application. We have a collection of `testCases` defined in line 1 that holds the current state. At line 20, we add a startup event handler that generates the initial application data and calls out to the function to update the test cases.

Pretty simple, though it does contain at least one bug (see if you can find it before I point it out later).

### Rendering JavaScript Code

Now, let’s look at our list rendering code. It’s not pretty since we’re not using a templating engine or a fancy single page application framework like Angular, Vue, or React.

{% gist https://gist.github.com/IntegerMan/def3d48a627beb9c258e5370668d1d31 %}

The code here is relatively self-explanatory and clears out the list of items, then adds each item to the list. I never said it was efficient, but it works for a demo.

Like the last, this chunk contains at least one bug.

### Event Handling JavaScript Code

The final chunk of code handles events from the user.

{% gist https://gist.github.com/IntegerMan/017c1f11802da7e25d41e885269abde3 %}

This specifically handles button clicks and adding items to the list.

And, again, there’s at least one bug in this chunk.

### What’s Wrong with the Code?

So, what’s wrong here? Well, I’ve observed the following problems:

- It’s impossible to fail or delete the initial test data.
- It’s impossible to fail any added test
- If you _could_ delete all items, the add item label wouldn’t show up

Where the bugs are isn’t the point. The point is: _each one of these bugs would have been caught by TypeScript._

So, with that introduction, let’s start converting this to TypeScript. In the process, we’ll be forced to fix each one of these defects and wind up with code that can’t break in the same way again.

## Installing TypeScript

If you have not already installed TypeScript, you will need to install [Node Package Manager (NPM)](https://nodejs.org/en/) before you get started. I recommend installing the Long Term Support (LTS) version, but your needs may be different.

Once NPM is installed, go to your command line and execute the following command: `npm i -g typescript`

This will **i** nstall TypeScript **g** lobally on your machine and allow you to use `tsc`, the **T** ype **S** cript **C** ompiler. As you can see, although the term for converting TypeScript code to JavaScript is _transpiling_, people tend to say compiler and compilation. Just be aware that you may see it either way – including in this article.

With this complete, you now have everything you need in order to work with TypeScript. You don’t need a specific editor to work with TypeScript, so use whatever you like. I prefer to work with [WebStorm](https://www.jetbrains.com/webstorm/) when working with TypeScript code, but [VS Code](https://code.visualstudio.com/) is a very popular (and free) alternative.

Next, we’ll get set up with using TypeScript in our project.

## Compiling our Project as a TypeScript Project

### Initializing TypeScript

Open a command line and navigate into your project directory, then run the following:

`tsc --init`

You should get a message indicating that `tsconfig.json` was created.

You can open up the file and take a look if you want. Most of this file is commented out, but I actually love that. TypeScript gives you a good configuration file that tells you all the things you can add in or customize.

Now, if you navigate up to the project directory and run `tsc` you should see TypeScript displaying a number of errors related to your file:

![](https://i1.wp.com/killalldefects.com/wp-content/uploads/2019/10/image-25.png?fit=770%2C446&ssl=1)

These issues are all valid concerns, but for the moment, let’s disable some by editing the tsconfig.json file and setting `"strict": false,`.

Now, if you try to compile, you’ll get a much smaller subset of errors. Most of them seem to be around the `TestCase` class, so let’s take a look at that now.

### Type Annotations

Most of the errors seem to be around `isPassing` and `id` not being defined on that class. That makes sense since we were using JavaScript’s innate ability to dynamically define properties. Since we’re using TypeScript’s checking, we’ll need to define those fields now:

{% gist https://gist.github.com/IntegerMan/43a89163518c30b73ba2e9649e21e82f %}

Lines 8-10 are new here and define the missing fields. Note that we have type annotation syntax here in the `: string`, `: boolean`, and `: number` definitions.

### Type Assertions

Next, we’ll address an issue in the `addTestCase` method. Here, TypeScript is complaining that `HTMLElement` doesn’t have a `value` field. True, but the actual element we’re pulling is a text box, which shows up as an `HTMLInputElement`. Because of this, we can add a _type assertion_ to tell the compiler that the element is a more specific type.

The modified code looks like this:

`const textBox = <HTMLInputElement>document.getElementById('txtTestName');`

_ **Important Note:** TypeScript’s checks are at compile time, not in the actual runtime code. The concept here is to identify bugs at compile time and leave the runtime code unmodified._

### Correcting Bad Code

`TSC` also is complaining about some of our `for` loops, since we were cheating a little and omitting `var` syntax for these loops. TypeScript won’t let us cheat anymore, so let’s fix those in `updateTestCases` and `findTestCaseById` by putting a `const` statement in front of the declaration like so:

```ts
function findTestCaseById(id) {
    for (const testcase of this.testCases) {
        if (testcase.id === id) return testcase;
    }

    return null;
}
```

### Fixing the Bugs

Now, by my count, there are two more compilation issues to take care of. Both of these are related to bugs I listed earlier with our JavaScript code. TypeScript won’t allow us to get away with this, thankfully, so let’s get those sorted out.

First of all, we call to `showAddItemsPrompt` in `updateTestCases`, but our method is called `showAddItemPrompt`. This is an obvious issue, and one that could conceivably be caused either by a typo or renaming an existing method but missing a reference. This is easily changed by making sure the names match.

Secondly, `failTestCase` declares a variable called `testCase` and then tries to reference it as `testcase`, which is just never going to work. This is an easy fix where we can make sure the names are consistent.

## Referencing our Compiled Code

And, with that, running `tsc` results in no output – that mean our code compiled without issue!

On top of that, because Logic.ts will automatically transpile into `Logic.js`, the file our `index.html` is referencing anyway, that means that we don’t even have to update our HTML.

And so, if we run the application, we can see that we can fail and delete tests again:

![An animated gif showing that failing, passing, and deleting new items works](https://i1.wp.com/killalldefects.com/wp-content/uploads/2019/10/TypeScriptFixed.gif?w=770&ssl=1)

But wait, weren’t there three errors in the code? TypeScript only found two!

Well, yes, but we haven’t told TypeScript enough to find the third one yet. Let’s fix that by re-enabling strict mode.

## Strict Mode

Going back into `tsconfig.json`, set `strict` to `true`.

This should yield about 16 errors during compile. The vast majority are _no impicit any_, or TypeScript complaining that it doesn’t know what type things are. Going through and fixing that is somewhat straightforward so I won’t walk through it, but feel free to check my finished result if you get lost.

Beyond that, we see a few instances where TypeScript points out that things could be null. These involve fetching HTML Elements from the page and can be resolved via type assertions:

`const list = <HTMLElement>document.getElementById('listTestCases');`

The type assertions are acceptable here because we are explicitly choosing to accept the risk of a HTML element’s ID changing causing errors instead of trying to somehow make the app function without required user interface elements. In some cases, the correct choice will be to do a null check, but the extra complexity wasn’t worth it in a case where failing early is likely better for maintainability.

### Removing Global State

This leaves us with 5 remaining errors, all of the same type:

`'this' implicitly has type 'any' because it does not have a type annotation.`

TypeScript is letting us know that it is not amused by our use of this to refer to items in the global scope. In order to fix _this_ (no pun intended), I’m going to wrap our state management logic into a new class:

{% gist https://gist.github.com/IntegerMan/eff99c24bd35a0bfc8b1e4eda83b5537 %}

This generates a number of compiler errors as things now need to refer to methods on the `testManager` instance or pass in a `testManager` to other members.

This also exposes a few new problems, including that bug I’ve alluded to a few times.

Specifically, when we create the test data in `buildInitialData` we’re setting the `id` to `'1'` instead of `1`. To be more explicit, `id` is a `string` and not a `number`, meaning it will fail any `===` check (though `==` checks will pass still). Changing the property initializer to use the number fixes the problem.

_ **Note:** This problem also would have been caught without extracting a class if we had declared type assertions around the `testcases` array earlier._

The remaining errors all have to do with handling the results of `findTestCaseById` which can return either a `TestCase` or `null` in it’s current form.

In TypeScript, this return type can be written explicitly as `TestCase | null`. We could handle this by throwing an exception instead of returning null if no test case was found, but instead we should probably heed TypeScript’s advice and add null checks.

I’ve glossed over many details, but if you’re confused on something or want to see the final code, it is [available in my GitHub repository](https://github.com/IntegerMan/MigratingToTypeScript/tree/migrateEnd).

## Benefiting from TypeScript

Now, when we run the application, the code works perfectly

![The JavaScript app after being migrated to TypeScript. All functionality works as expected.](https://i2.wp.com/killalldefects.com/wp-content/uploads/2019/10/TypeScriptFixed2.gif?w=770&ssl=1)

Not only that, the compiler itself makes sure that the errors we encountered will never be possible again (if we keep playing by the rules, anyway).

Additionally, TypeScript helped us gracefully handle potential errors down the road by forcing us to think about potentially null values.

## Next Steps

If you’re interested in getting more in-depth with TypeScript, stay tuned as I intend to cover more topics of note including:

- Linting to find additional issues
- Testing TypeScript with Jest
- Automatically formatting code with Prettier
- Bundling files together
- Using NPM and WebPack to manage complex build processes

If you’d like to start with a fresh project already set up for these things, I recommend you check out [Christoffer Noring’s](https://twitter.com/chris_noring) [TypeScript Playground repository on GitHub](https://github.com/softchris/typescript-playground).

## Closing Thoughts

There’s been a recent surge of people attacking TypeScript for getting in the way, obfuscating JavaScript, being unnecessary, etc. And sure, maybe TypeScript is overkill for an app of this size, but here’s where I stand on things:

TypeScript is essentially a giant safety net you can use when building JavaScript code. Yes, there’s effort in setting up that safety net, and no, you probably don’t need it for trivial things, but if you’re working on a large project without sufficient test coverage, you _need_ some form of safety net or you’re going to be passing off quality issues to your users.

To my eyes, TypeScript is an incredibly valuable safety net that supports existing and future unit tests and allows QA to focus on business logic errors and usability instead of programming mistakes.

I’ve [taken a large JavaScript application and migrated it to TypeScript](https://killalldefects.com/2019/09/22/how-typescript-squashed-all-known-bugs/) before to great effect. In the process, I resolved roughly 10 – 20 open bug tickets because TypeScript made the errors glaringly obvious and impossible to ignore.

Even better, this process made the types of errors that had occurred anytime the app was touched _impossible_ to recur.

So, the question is this: What’s your safety net? Are you really willing to let language preferences pass on defects you might miss to your end users?

The post [Migrating to TypeScript](https://killalldefects.com/2019/10/27/migrating-javascript-to-typescript/) appeared first on [Kill All Defects](https://killalldefects.com).