How I rewrote our codebase to TypeScript in a week

How hard could it be? 🤔

"TypeScript is JavaScript, only with types." 📸: Victor Xok / Unsplash
"TypeScript is JavaScript, only with types." 📸: Victor Xok / Unsplash Vis mer

As a newly recruited consultant on a Norwegian government project to digitalize the communication between individuals, the government and businesses I was greeted with a React app all written in JavaScript, and developers with more experience than me said it would take weeks or months to rewrite our app to TypeScript. I realized that many people think of TypeScript as a whole new language and not a typed superset of JavaScript.

TypeScript is JavaScript, only with types.

«But what are types? How can it just be added to JavaScript?»

But what are types? How can it just be added to JavaScript? Types define what kind of values an object, variable or constant can be assigned. JavaScript doesn’t care about that, that’s why TypeScript “encapsulates” your code and gives you compilation errors when you assign a string to a variable that is defined with number type.

In the end, TypeScript is only for the developers. Since it gets compiled to JavaScript when you’re building the application.

But why do we need types?

You might ask yourself “But if it’s only for me as a developer, do I need it?”. The answer is probably no if you’re the only one working on a small project. But at our project, we are over 10 consultants working on the same codebase. If you think of it and you come over a function like this

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

It does seem like a simple function, right? It adds two values and returns it. So you’re going to use this function with two parameters that you get from another function.

import { add } from './utils';
function getAndAddValues(a, b) {
  let sumOfValues = add(a,b);
}

What happens if your colleague sends in an object to “getAndAddValues”? Where is the warning when you’re only using JavaScript? It’s no warning, it’s a bug, and it’s out in production.

TypeScript wouldn’t compile this piece of code if you’ve typed your code correctly. And then you would’ve saved your team a bug in production.

So, how do I rewrite my application to TypeScript?

When I write “rewrite” it’s not to start from scratch, because I said that TypeScript is JavaScript, just with types right?

The most time-consuming job is actually to rename all your JavaScript files (.js/.jsx) to TypeScript files (.ts/.tsx):

App.jsx -> App.tsx

If you’d do that right away you might run into bundling errors with Webpack. Because you haven’t defined any rules for TypeScript files in your “webpack-config.js”-file.

If you’re using “Create React App” you might have to eject your app for this. Or rewire your app with react-app-rewired.

My opinionated recommendation is to use awesome-typescript-loader but there are other options such as ts-loader.

Follow the steps for the TypeScript loader of your choice. And you’re set. Or are you? You’ll need one more file in the root of your project. You’ll need a “tsconfig.json”-file.

This file is the configuration of typescript for your project, it can be very strict or not so strict. Here’s the content of a basic configuration of TypeScript in tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "outFile": "./dist/index.js",
    "sourceMap": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "**/*.spec.ts"
  ]
}

Read more about the settings you can define in this file here.

So, you think you’re all set now? Not quite. You’ll need typescript installed. That’s as simple as a:

npm i typescript -D

And now, your files will be compiled. Or at least webpack will compile if you don’t have any errors, based on a basic application you’ll have a few errors. Mostly that you are missing typings for the dependencies your app has defined in package.json.

So, you got me to break the whole project? What now?

This is where I leave you… I’m just kidding, I’ll hold your hand through this transition. So, you’ll need typing-packages for your dependencies. I’m guessing you have react and react-dom as dependencies? Then you need:

npm i @types/react @types/react-dom -D

The “@types/” prefix installs a package in a folder called @types in your node_module folder. It is used by TypeScript to check types when you are importing and using external packages. Almost all of the packages you use has a typings-package prefixed with @types/{package-name} . Some packages even supply own types, which is cool.

So now all your external typings from dependencies are installed and you’re ready to write some of your internal typings.

Let’s get going! This is why you’re here! (I think 🤔)

Let’s start with typings for variables and constants.

// from this
let name = 'Jesper';
// to this
let name: string = 'Jesper';

You can define variables or constants as a string, number, boolean or any. (Don’t use any, it’s like saying you like ice cream, but you like it warm… But seriously it’s like you’re trying out TypeScript without types, why?).

Let’s see, how do you write typings for objects? Easy, you declare your interfaces. Let’s do a simple person-example.

interface IPerson {
  firstName: string;
  lastName: string;
  age: number;
  isCool: boolean;
}
const person1: IPerson = {
  firstName: 'Jesper',
  lastName: 'Førisdahl',
  age: 25,
  isCool: true,
};

You define the keys the object will have, and the type of value they are expecting to be.

"But Jesper, this is all good… But how do I use TypeScript with React?"

Yes, I’m talking to myself in third-person. Deal with it.

Easy. Like this:

import React from 'react';
// Declare types of props
interface IProps {
  text: string;
}
// Declare types of state
interface IState {
  text: string;
}
// Old stateful declaration
export class TypedComponent extends React.Component<IProps, IState> {
  constructor(props: IProps) {
    super(props);
    this.state = {
       text: props.text,
    };
    ...
  }
  
  ...
render(): JSX.Element {
    return (
      <div>
        {this.state.text}
      </div>
    );
  }
}
// New declaration with hooks
export function TypedComponent(props: IProps) {
  const [text, setText] = React.useState(props.text);
return (
    <div>
      {text}
    </div>
  );
}

Probably a little harder than me just saying “easy”. But with the old way of declaring components with classes you need to provide the typings for the props and state in the <…> in that order.

But what about optional values in an object?

It’s as simple as adding a `?` after the key name.

interface IProps {
  text: string;
  additionalText?: string;
}

So, now you have a basic setup and introduction to TypeScript, do you feel confident enough to rewrite your project?

At our project, we use redux with redux-saga, so if you are interested in an article in this format about how we write typings for this, comment below! 😁

🖐🎤