How to migrate from JavaScript to TypeScript?

 
 
  • Gérald Barré

After the previous posts about TypeScript, I'm sure you'd like to migrate your application from JavaScript to TypeScript. If you haven't read them yet, take a few minutes:

The migration from JavaScript to TypeScript is not a very complicated process. But there are some steps to follow to achieve it correctly without spending too much time.

#Learn the TypeScript language

Of course, you have to start learning the language. If you are already a JavaScript developer, learning TypeScript is easy.

Here are a few good links:

You'll also find useful resources on this blog 😃

#Install TypeScript

If you are using Visual Studio, the TypeScript compiler is already installed. In other cases, you must install it.

Shell
npm install typescript
.\node_modules\.bin\tsc --version

or you can install it globally

Shell
npm install -g typescript
tsc --version

#Add the TypeScript configuration file to your project

The configuration file indicates to the TypeScript compiler the options to use to compile the project. This is also the root of the TypeScript project.

Your solution should look like:

Root
├── src
│   ├── file1.js
│   └── file2.js
├── built
└── tsconfig.json

The tsconfig.json at the root of the project contains:

JSON
{
  "compilerOptions": {
    "target": "es5",
    "allowJs": true,
    "outDir": "./built"
  },
  "include": [
      "./src/**/*"
  ]
}

The file indicates the TypeScript compiler to accept JavaScript files, so you don't need to change all your code at once. With this option, you can convert your files one by one. The outDir indicates where TypeScript outputs the files after the compilation. The include options indicate where are located the source files.

By default, the compiler won't analyze the error in the JS files. You can start getting some of the benefits of TypeScript by indicates the compiler to check your JS files using "checkJs": true:

JSON
{
  "compilerOptions": {
    "target": "es5",
    "allowJs": true,
    "checkJs": true,
    "outDir": "./built"
  },
  "include": [
      "./src/**/*"
  ]
}

The compiler will use the information it can gather from your files to indicate errors (documentation). For instance, it can use JSDoc to find types or resolve require("...") statements. So, You'll get some of the TypeScript advantages immediately. You can also set some compiler options such as noImplicitReturns: true, noFallthroughCasesInSwitch: false, allowUnreachableCode: false or allowUnusedLabels: false.

JavaScript
/** @type {string} */
var author;

author = "meziantou"; // OK
author = false;       // Error: boolean is not assignable to string

#Convert files to TypeScript and fix common errors

It's time to start using TypeScript. The only change required is to change the extension of the files from .js to .ts. You don't need to migrate all files at once. You can migrate files one by one. If you have a large code base, you'll probably get hundreds of errors. So, converting files one by one will allow us to handle them more easily.

Some valid JavaScript will stop working in TypeScript. Mainly because of the type checker. Here are a few errors you may encounter.

##Sequentially Added Properties

The following code is very common in JavaScript:

TypeScript
var author = {};
author.nickname = "Meziantou";
author.fullName = "Gérald Barré";

In TypeScript the type of author is an empty object. So nickname and fullname doesn't exist and are not assignable. The solution is to move the declarations into the object creation:

TypeScript
var author = {
    nickname = "Meziantou",
    fullName = "Gérald Barré"
};

The other solution is to create a type for the author variable, so the compiler knows the list of properties available.

TypeScript
interface Author { nickname: string; fullName: string }

var author = {} as Author;
author.nickname = "Meziantou";
author.fullName = "Gérald Barré";

The latest option, which is not the best, is to indicate the compiler to not check the usages of the author variable using the type any:

TypeScript
var author: any = {};
author.nickname = "Meziantou";
author.fullName = "Gérald Barré";

##Using a well-know library

TypeScript must know the types of every objects to compile a file. So, if you are using a library such as JQuery, loadash, etc., you have to tell TypeScript what the library contains. Fortunately, the TypeScript definitions of thousands of lib are available via npm. For instance, if you use jquery, you can install the definition using

Shell
npm install @types/jquery

You'll find the complete list of supported libraries on npm: https://www.npmjs.com/~types

##Using others libraries

If the types are not available, you have 2 options:

  • Create the definition of the methods/objects
  • Create a dummy declaration

For instance, you use a lib that exposes a function add(a, b) and an object _, you can create the following dummy declaration, so the compiler is happy. The any type indicates to the compiler to not check the value.

TypeScript
declare function add(...args: any[]): any;
declare var _: any;

add(1, 2);
_.filter(characters, { 'age': 36 });

With these definitions, anything after _ will be valid. While this is not the philosophy of TypeScript, it's better to have 80% of the code checked instead of 0%. So, it's a good start. Later, you can write a better definition such as:

TypeScript
declare function add(a: number, b: number): number;
declare var _: Underscore;

interface Underscore {
    filter(obj: any[], filter: { [name: string]: any });
}

##External modules (require, define)

If you are using modules with commonjs, requirejs or amd, you can use a dummy declaration:

TypeScript
declare function require(path: string): any;
declare function define(...args: any[]): any;

However, you should use the TypeScript syntax. This is more convenient and you'll get the typing information. First, change the module option in the tsconfig.json file:

JSON
{
  "compilerOptions": {
    "module": "commonjs" // Set the kind of module you are using
  }
}

Then change your import statements:

TypeScript
// JavaScript
var math = require("./math");

// TypeScript (one of the following)
import math = require("./math");
math.add(1, 2);

import { add } from "./math";
add(1, 2);

You can also replace the exports with the new syntax

TypeScript
// JavaScript
module.exports.add = function(a, b) {
    return a + b;
}

// TypeScript
export function add(a, b) {
    return a + b;
}

#Use new syntaxes

You know have a valid TypeScript project. You can start using the new TypeScript language features. This should help you write a more readable code. For instance, you can:

  • Replace immediately-invoked function expressions with namespace/module
  • Use classes when possible
  • Replace var with let or const
  • Replace magic numbers/strings with enumeration
  • Replace require() with import (check the previous section)
  • Use async await to simplify the usage of asynchronous code
  • Replace property names with nameof equivalent

Some tools such as Resharper allows to replace common JS syntax with a TypeScript equivalent: https://blog.jetbrains.com/dotnet/2015/02/05/ways-and-advantages-of-migrating-javascript-code-to-typescript/

#Enable compiler checks to find more errors

Finally, you should change the compiler options to enable more and more checks. This will help you find more errors at compile time. Some of them such as strictNullChecks will generate lots of errors (maybe thousands). So, you should enable these options one by one and fix the new errors. You can check how the VS Code team handled this problem in this issue.

JSON
{
    "compilerOptions": {
        "allowUnreachableCode": false,
        "allowUnusedLabels": false,
        "alwaysStrict": true,
        "noImplicitAny": true,
        "noImplicitReturns": true,
        "noImplicitThis": true,
        "noFallthroughCasesInSwitch": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "suppressExcessPropertyErrors": false,
        "suppressImplicitAnyIndexErrors": false,
        "strictFunctionTypes": true,
        "strictNullChecks": true,
        "strictPropertyInitialization": true,
        "checkJs": true // If you are still using some js files
    }
}

You can read more about all these options in a previous post: Detect common JavaScript errors with TypeScript.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub