Skip to main content
All CollectionsTech Focus
How to Migrate a Javascript Codebase to Typescript
How to Migrate a Javascript Codebase to Typescript

Many developers appreciate the extreme flexibility that Javascript provides. However, after numerous runtime errors, we may wish to improve.

Alex avatar
Written by Alex
Updated over a week ago

As developers working with Javascript, many of us can appreciate the extreme flexibility that the language provides. That said, I am sure that after a healthy amount of runtime errors resulting from undefined fields on objects we are left wondering, “can we not do better?” - even for seasoned developers.

This is where Typescript comes to the rescue.

According to the official docs, TypeScript is a superset of JavaScript with syntax for types. It allows us to introduce type safety similar to that provided in many system programming languages, which effectively allows us to prevent many typical Javascript runtime bugs at "compile time".

To set up Typescript for a brand new project is easy and there are many tutorials out there that demonstrate this. However, we often work with existing Javascript codebases in need of a makeover into Typescript. In this article I will show you an approach for converting a pure Javascript codebase to Typescript in a few simple steps:

  • Javascript codebase setup

  • Install Typescript

  • Typescript setup

As a bonus, at the end of the article, I will also show you how to optimise the Typescript build for your project to achieve faster compilation time with the Speedy Web Compiler (SWC). SWC is a Rust-based compilation and bundling tool that can be used to replace the native Typescript compiler. It also claims to be 20x faster than Babel (on a single thread)!

Javascript Codebase Setup

To complete the code base setup in this section, you will need to have Node.js installed on your machine.

Pro tip💡: You can find the Node installer relevant to your OS here if required.

First mission

We need some Javascript so that we can allow Typescript to shine. In this article, we will set up a simple API server with Express, which is a good example as we get to see how to convert third party packages to Typescript. The structure of the codebase is shown below:

| - js-to-ts

| - node_modules

| - src

| - routes

| - index.js

| - route_one.js

| - route_two.js

| - app.js

| - package.json

We will first set up the above codebase by creating the relevant folders and installing Express. Follow the steps below:

  1. In a directory where you would like to set up the codebase, run mkdir js-to-ts then cd js-to-ts .

  2. Inside js-to-ts directory, run the command npm init to initialise a node project environment then run mkdir src.

  3. Once the node project environment is setup, install Express by running npm install express .

  4. Inside js-to-ts directory, setup relevant server code by running touch app.js -> mkdir routes -> cd routes -> touch index.js route_one.js route_two.js.

At this point, the structure of the codebase is complete. Since this is not an Express tutorial, I will just illustrate the file contents below with minimal explanation.

You can find the complete codebase used in this article here. If you need a refresher on Express please consult the official docs.

Note 📝: within package.json we created a startup script start so we can spin up the Express server using the command npm run start.

With the above codebase setup in pure Javascript, we are now ready to start the Typescript conversion process in the next section!

How does Typescript work?

Before we dive into Typescript setup, let us first gain a basic understanding of what exactly it is.

TypeScript is a superset of JavaScript.

It essentially introduces a compilation step to Javascript where Typescript code gets compiled to Javascript code. During this process, the compiler checks for type safety and warns us if any constraints are violated.

It is helpful to understand that all Javascript programs are also Typescript programs, which means the Typescript compiler can simply skip Javascript files given appropriate compiler settings.

Typescript Installation

Now that we have a better understanding of how Typescript works, let’s install it in our project environment:

  1. Inside the js-to-ts directory from the previous section, run the command npm install --save-dev typescript. This will install Typescript as a development dependency for your project which will not affect your production builds.

  2. Run npm install --save-dev @types/node. This will install all types for built-in node packages as a development dependency. At this point, your package.json should look similar to the one below.

That is all there is to installing Typescript, piece of cake right? There is one thing to note here though:

Some of the tutorials out there suggest installing Typescript globally using npm install -g typescript which will allow you to use the compiler for any project without having to install it in your local project environment. I personally prefer installing Typescript locally for every project to keep my dependencies clean.

When we install Typescript locally we need to run the compiler via its absolute path. For example, to check if the compiler is correctly installed we’d have to run this command inside the project directory ./node_modules/typescript/bin/tsc --version. But this is rather verbose. An easier alternative is to use the node package runner to run a locally installed package i.e. npx tsc —-version.

Now that we have installed Typescript let’s proceed to the actual setup in the next section!

Typescript Setup

The first step is to initialize Typescript in your project environment. Inside the js-to-ts directory, run npx tsc --init. This will simply add a tsconfig.json file at the root level of your project. Inside this file, you will specify configurations for the Typescript compiler to use when converting files in your codebase.

The tsconfig.json file comes with all available settings mostly commented out. We will make some changes and the resulting configurations are shown below for this project. Descriptions for each setting are documented on each line.

Note 📝: For a backend node environment, the target setting can be set to a more modern syntax such as es2016.

We would typically set it to es5/es6 for client-side projects with Typescript for better browser version support.

Note 📝: For an existing Javascript project, it is crucial that we set the allowJs option as true since we often need to incrementally convert our codebase to Typescript.

If we compile the project based on the above settings, all JS or TS files inside the src directory would be converted and emitted to the dist directory. If dist does not exist then it will be automatically created. To do this we just need to run npx tsc inside the js-to-ts directory. A demonstration is shown in Fig.1 below.

From above we see that although our codebase only has .js files, we were still able to compile the codebase and this is the key in converting an existing codebase to Typescript. Note the additional .js.map files emitted by the compiler, these are source map files that create memory mapping between the output .js files and the input .js/.ts files in order to allow debugging directly in the original input files.

Now that we have Typescript installed and relevant settings setup in tsconfig.json , we can incorporate the compilation process with the startup script in package.json. The updated package.json is shown below.

Note we now have two scripts:

  1. build : This script removes the content from the compilation target folder then compiles the codebase emitting all output files to the target folder.

  2. build-and-start: This script runs the build script first as the compilation stage then starts the express server via the entry dist/app.js.

To start up the server, including compiling the codebase, we simply just run npm run build-and-start from the terminal inside of our project root directory.

Typescript Conversion

We are now finally ready to convert the codebase to Typescript! 😁

The first step is straightforward, let’s change all the file extensions from .js to .ts as shown below in Fig.2.

We can see that the native VSCode Typescript checker already picked up errors in these files after the extension changes. We will proceed to make all the changes to resolve these errors and make our codebase Typescript ready!

In order to allow for proper type checking to happen, we need to install type definitions for the express package. We can do that by running npm install --save-dev @types/express inside of our project root directory.

Let’s go through every file we have in the codebase and discuss the relevant Typescript changes starting with app.ts as shown below.

All that’s changed in app.ts is that the syntax for module management has been changed from commonJS to ES Modules. This is not a necessary change but I personally prefer the more modern ES modules syntax as it allows us to keep code consistent between client and server.

Changes within the server routes files are very similar so they are all shown together below.

Note that in each file, we changed the module syntax to ES modules using import and export default. We also explicitly defined the callback function signature for each server route, where the callback argument type definitions are directly imported from Express.

Since this is not a fundamental Typescript tutorial, I will not cover all the wonderful type-checking features you now have available. However, I will at least use a simple demonstration below to explain why we should use Typescript.

Above, we tried to invoke a method respond() on the res input to the route callback in index.ts. Note how the VSCode Typescript checker immediately picks up that the method does not exist on the Response type which is assigned to res. This is an extremely powerful tool since we can now write code knowing how objects, functions and classes within the same codebase should be used provided that we have proper types defined.

We now have a fully functioning Typescript project. Although it’s a very basic setup, the concepts demonstrated are easily translatable for larger projects. Of course compiler configurations and other tools can be incorporated as per your project specific requirements.

Before we head off, I’d like to show you an alternative setup I came across recently which shows promise. This setup uses the Speedy Web Compiler (SWC) for compilation whilst still ensuring type safety in the codebase by using Typescript. Why would we want to use SWC for compilation you ask? Well, this compiler (and bundler) tool is written in Rust which is a super performant language and on their official site they claim it to be 20x faster than Babel! I don’t know about you but that’s a good enough reason for me to experiment. Let’s dive into SWC setup in the next section.

Faster Compilation with SWC

The first step as always is to install SWC within your project. For this run the following command.

npm install --save-dev @swc/cli @swc/core

Once installed, we can set up SWC by placing a .swcrc file within the root directory of the project and it should look like the one shown below.

The above-shown settings are pretty standard for a basic project setup. A few settings are especially important, so I will briefly explain them below:

“syntax”: “typescript” -- This setting tells the SWC parser to support Typescript files during compilation.

“target”: “es2020” -- The emitted files should be compiled to Javascript es2020 syntax. For a Node backend project this is preferred, but if we wanted to use SWC as a tool for the frontend it is recommended to set the target syntax to “es6” or “es5”. This will allow support for a wider range of browsers as well as browser versions.

“sourceMaps”: “inline” -- This creates memory mapping for the emitted Javascript files so that we can debug within the source Typescript files.

For a comprehensive overview of all possible settings/features for SWC, please do visit their official documentation here.

The next step is to adjust our tsconfig.json to disable Typescript compiler from emitting code so that we only use SWC for compilation. This way the Typescript compiler is still responsible for checking types within the codebase. The key here is to set “noEmit” to true, this will disable the native Typescript file conversions. The updated tsconfig.json file is shown below.

Lastly, we just need to incorporate SWC compilation step into package.json scripts so that we can start up our server in a seamless manner. For this, we will change the build script in package.json from:

rm -rf ./dist && npx tsc

to

rm -rf ./dist && npx tsc && npx swc src —-config-file .swcrc -d dist

Let’s break down the added command for SWC for a clear explanation:

  • npx swc src tells the SWC compiler to transform all files within the src directory.

  • --config-file .swcrc tells the SWC compiler to use settings specified in .swcrc for compilation. This is useful for incorporating npm scripts for different environments such as development or production.

  • -d dist tells the SWC compiler to place all emitted Javascript files inside the dist directory.

Now that we are all set with an even more optimised project setup using Typescript! Let’s see it in action below, note all 4 files within the codebase were compiled by SWC.

Of course, using SWC might be overkill for a small codebase, especially when it requires some additional setup. However, for large projects, the compilation speed boost from SWC should not be ignored. I will leave the benchmarking up to you; leave a comment about your experience with this setup!

Summary

In this article, we successfully converted a Javascript codebase to Typescript, enabling the language's wonderful safety features. In addition, we demonstrated an alternative and perhaps more optimised project setup with SWC replacing the native Typescript compiler.

Although this is not an in-depth Typescript tutorial, I hope this article sheds some light in terms of how we actually get a Typescript project going with an existing Javascript codebase!

About the Author

Xiao Ming (Mason) Hu has a Masters degree in Electrical Engineering. He loves expanding his knowledge and is into fitness and cryptocurrencies.

Did this answer your question?