How to Easily Migrate From JavaScript to TypeScript
Join the DZone community and get the full member experience.
Join For FreeIf you’ve been working in the software development industry (especially if you're doing a lot of frontend work) in the past couple of years, it’s most likely you worked, or still do, on a project in JavaScript. And by now, you’re more than tired from it. Therefore, in this article, I would like to talk about how to migrate from JavaScript to TypeScript.
Don’t get me wrong, JavaScript is an extremely flexible and easy to learn and use language, with one of the biggest communities out there. But, it also comes with a bunch of pitfalls you eventually end up running into, like it’s loose typing that can result in “odd” behavior. And there are some really interesting languages that transpile to JS and can be used on top of it, such as Dart, Elm, or TypeScript, just to name a few.
You may also like: New Features in TypeScript 3.7 and How to Use Them.
How to Easily Migrate from JavaScript to TypeScript
It’s much simpler to abandon JS when starting a new project, where you don’t have to worry about things like retro-compatibility, or an app’s maintenance in production. In that case, you can try many alternatives, and choose the one you like the best.
Nevertheless, if you must continue to work on an old project, you can begin to migrate from JavaScript to TypeScript, in a “slow” manner, file by file, or adding TypeScript only in the new files you create.
This is possible because TypeScript is a superset of JavaScript, meaning that any JS code is also valid TS code (assuming the TS configuration is set to be compatible with it).
Now, I will present a simple way to add TypeScript to a project, without the need to modify our configuration of webpack, gulp, or whatever our build system is.
Assuming you use npm as a package manager in your project, first thing we need to do is to add TypeScript as a dependency (if not, you can install it globally):
xxxxxxxxxx
npm install --save-dev typescript
Note: depending on what your project is, you might also want to install “@types” for other libraries you have dependencies on. For example, if you have a react-redux project, you might need to install the following:
xxxxxxxxxx
npm install --save-dev @types/node
npm install --save-dev @types/react
npm install --save-dev @types/react-dom
npm install --save-dev @types/react-redux
npm install --save-dev @types/react-router-dom
After that, we need to add a tsconfig.json file at the root directory of the project. That file contains compiler options needed to convert TS to JS. In order to have the least issues, use the following configuration to make JS code compatible with TS:
xxxxxxxxxx
{
"compilerOptions": {
"module": "commonjs",
"sourceMap": true,
"jsx": "react"
},
"exclude": [
"node_modules"
]
}
Note: You might need to change some bits based on your project. More on that here.
Now, add the following script in your package.json:
xxxxxxxxxx
"tsc:w": "tsc -w"
And run it. It will run a watcher that transpiles all .ts (or .tsx) files into regular .js files. Also, it will generate these files at the same path that of original; therefore, all imports and all build processes you might have will work as before, since the .ts files are completely ignored, and the result of the transpilation is used instead. The generated file structure has the following structure:
xxxxxxxxxx
file.ts
├── file.js
└── file.js.map
Now, all we need to do is to create our first ‘.ts’ file by changing the extension of an existing one that we want to migrate to TypeScript. Alternatively, we can also create a blank file to start working in our application.
This does not bring much change. We still can put normal JS code and get no help from what TypeScript has to offer. In order for TS to force us to actually type our code, we need to change the tsconfig.json file. In particular, we will focus on two compiler options that will force our code to be actually typed:
xxxxxxxxxx
"noImplicitAny": true,
"strictNullChecks": true,
Let’s imagine we have a simple mortgage simulator that tells the user if, given his financial situation, a mortgage is viable or not. For that, we will have a function that will retrieve somehow the savings user has:
xxxxxxxxxx
function getSavings() {
//returns savings
}
And a function to decide if a mortgage is feasible:
xxxxxxxxxx
function concedeMortgage(homeValue) {
const savings = getSavings();
return savings / homeValue > 0.2;
}
But, to make it actually work, we would need to check if the input exists. And also if it’s a number or not. The same applies for the return value from getSavings
, since we don’t know the return type of that function either. Therefore, our code could end up looking something like this:
xxxxxxxxxx
function concedeMortgage(homeValue) {
if(!homeValue || !parseFloat(homeValue)) return false;
const savings = getSavings();
if(!savings || !parseFloat(savings)) return false;
return savings / homeValue > 0.2;
}
This looks quite terrible.
But, if we activate the noImplicitAny
compiler option, it would no longer be necessary to check if the input value and the return from getSavings
are numbers, so the function could look something like this:
xxxxxxxxxx
function concedeMortgage(homeValue: number): boolean {
if(!homeValue) return false;
const savings = getSavings();
if(!savings) return false;
return savings / homeValue > 0.2;
}
This is an improvement, since the compiler can help us to avoid some mistakes and typos, not allowing us to call the function with a string for example:
xxxxxxxxxx
concedeMortgage("foo") // ERROR! Argument of type 'foo' is not assignable to parameter type 'number'
Unfortunately, null and undefined are still in the domain of every type, therefore it would be possible to do this:
xxxxxxxxxx
concedeMortgage(null) // Still works
To fix that, we need to activate the other option in the tsconfig.json file we mentioned, strictNullChecks
.
Now, doing the call to the function with null, would end up getting caught by the compiler:
xxxxxxxxxx
concedeMortgage(null) // ERROR! Argument of type 'null' is not assignable to parameter of type 'number'
That means that we don't need to check for null types, which simplifies the logic to something like:
xxxxxxxxxx
function concedeMortgage(homeValue: number): boolean {
const savings = getSavings();
return savings / homeValue > 0.2;
}
This is just a small glance at what TypeScript can give you if you migrate your project from plain JS to it.
Further Reading
Published at DZone with permission of Roman Predein. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments