How-To's

Ways and Advantages of Migrating JavaScript Code to TypeScript

Nowadays, more and more new languages are created with the aim of providing an alternative to JavaScript. One of these languages is TypeScript. It is compiled to readable JavaScript code; its main goals are to perform type checking at compile time and to support the object-oriented paradigm. In this post we will not discuss the pros and cons of concrete languages or type systems. Instead, we’ll describe an easy way of performing migrations to TypeScript, and demonstrate how the conversion from JavaScript to TypeScript helps discover interesting artifacts (potential errors) as well as making your code clearer.

Ways Of Migrating

In the JavaScript world, people like to use patterns to represent entities, but unfortunately, these are not supported on the language level with constructs such as classes with inheritance, modules and other sorts of abstraction. There are lots of ways to implement these patterns. Sometimes, this abundance of mechanisms for emulating classical OOP constructs leads to confusion. On the other hand, TypeScript explicitly supports these abstractions in its syntax. This is why the process of converting JavaScript to TypeScript makes your code much clearer. The most straightforward way is, of course, manual conversion (see, e.g., this article), but that takes a lot of time and resources. In this post we try using a semi-automatic tool for this and attempt to assess its advantages and disadvantages.

Using a Semi-Automatic Tool

We took some real-world industrial code: bits of JavaScript that are part of node.js framework. It is about 25k lines of JavaScript code with classes and other constructs. For migration to TypeScript, we used a new ability of ReSharper 9, which performs the transformation of the provided JavaScript code. This feature is capable of handling the following JavaScript patterns:

Internal modules
Immediately-invoked function expressions
Immediately-invoked function expressions with namespace object passed as an argument
External modules
AMD format
CommonJs format
Classes and inheritance
Prototype.js
Native class implementation
Misc
Function expression to lambda
Member assigned by a function expression to function member
Usage of parent class via invocation of call/apply to keyword super
Creating dynamic property to ambient declaration
Recovering types
Infer possible overload signatures by function body/vsDoc

In general, support for these patterns allows ReSharper to very quickly produce an OOP baseline implementation. This is the tool’s main advantage. As a result we get approximate TypeScript code, which requires some error correction before it can be put into production.

So, what errors did we get after performing the conversion? We got about 1,200 errors and, after a certain amount of classification (thanks to ReSharper’s Solution-Wide Analysis) we managed to pinpoint these particular types of errors:

  • “Cannot resolve symbol” and other related errors – 70% of cases

  • “Supplied parameters do not match any signature of call target” – 25% of cases

  • Other cases – 5%

The occurrence of the “lack of matched signature” errors is related to the fact that the inference of overload signatures is not always possible statically without special annotation libraries. See example below.

Original JavaScript code:

JavaScript to TypeScript converter: original JavaScript code

Generated TypeScript code:

JavaScript to TypeScript converter: generated TypeScript code

But this is not the only source of problems. The next example illustrates a more frequent situation:

 

You may notice that the fail function does not declare optional parameters. That is why ReSharper is unable to infer the overload signature with optional parameters. This code can be confusing, and may cause errors.

The most frequent type of error, however, is the inability to resolve a particular symbol, as well as related issues. These also appear because of dynamic nature of the language:

In general, these sorts of errors are expected. At the moment, it is the developer’s responsibility to fix these errors but, in the future, ReSharper might be able to resolve them automatically.

Errors from the other category are more interesting. Some of them shows that the lack of common convention leads to confusing. The next example shows a typical deviation from convention CommonJs modules.

Declaration of Buffer as exported variable:

Usage of the Buffer symbol, which was not imported:

Symbol Buffer is global type, by design, of node.js, but it is not obvious by code.

The next example demonstrates multiple inheritance. If your parent classes have properties with the same name and you don’t notice it, it obviously leads to undefined behavior. The compiler can’t help you either. And if you use multiple inheritance in code, you will have to replace it on composition yourself. TypeScript doesn’t support it.

Original JavaScript code:

multiple inheritance in JavaScript code

Generated Ts code:

multiple inheritance in generated TypeScript code

How To Perform Conversion?

Obsolete code can be converted with ReSharper’s highlightings and the corresponding quick-fixes. There are different granularities for conversion:

  • Element under caret
  • In file
  • In folder
  • In project
  • In solution

Converting JavaScript to TypeScript with ReSharper

In conclusion, this tool speeds up the process of converting JavaScript to TypeScript. Give it a try!

image description