How to migrate from JavaScript to TypeScript?
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.
- Install
nodejs
: https://nodejs.org/en/ - Install TypeScript in the project or globally
npm install typescript
.\node_modules\.bin\tsc --version
or you can install it globally
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:
{
"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
:
{
"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
.
/** @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:
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:
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.
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
:
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
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.
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:
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:
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:
{
"compilerOptions": {
"module": "commonjs" // Set the kind of module you are using
}
}
Then change your import statements:
// 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
// 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
withlet
orconst
- Replace magic numbers/strings with enumeration
- Replace
require()
withimport
(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.
{
"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!