Stephen Hartfield Stephen is a self taught, full stack developer. He lives in St. Louis, MO with his wife and two children. He enjoys reading, writing, playing video games, playing basketball, and being a father.

A simple guide for migrating from JavaScript to TypeScript

9 min read 2546

TypeScript logo against a black and white background.

As you probably already know, TypeScript is a popular Static Type Checker, built as a superset of JavaScript. TypeScript is really handy for finding type errors before executing them — a rule that is missing from plain Javascript. However, Typescript runs just as smooth as Javascript.

We will assume you started a project in JavaScript and it is too late to start over. So, you have a problem: you want to work in TypeScript, but you are too far along to restart.

What do you do?

In this article, we will cover a way to smoothly and efficiently migrate over to Typescript.

When migrating over to TypeScript, one basic thing to keep in mind is TypeScript files have a .ts extension instead of .js. That’s simple enough. Another thing to keep in mind that makes migrating over a breeze is that you can configure your TypeScript compiler to allow JavaScript files.

As we said earlier, TypeScript is just a superset on top of Javascript — therefore, you can migrate one file at a time and not worry about creating an entirely separate branch for the migration.

Speaking of compilers, it would be nice to leave alone whatever currently bundles or compiles your JavaScript. TypeScript has an easy way to simply add itself on top of your compiler without messing things up. If you’re using npm, you can simply run npm i -g typescript.

Before getting further into the TypeScript set up, one thing to consider is whether your IDE is configured to work with TypeScript. The pre-compile auto-correct TypeScript offers, as well as the ability to detect errors at this stage, are features you do not want to miss out on. If you are using VS Code, support for TypeScript is built in. Otherwise, you will likely need to find a package to get your IDE support for TypeScript.

Typescript compiler

Now you’ll want to add the TypeScript compiler.

Create a file in the root directory of your project named tsconfig.json. There are hundreds of ways to configure this file, but let’s just look at some of the basics.

We made a custom demo for .
No really. Click here to check it out.

Before we go into it a bit, instead of creating the file yourself, you can run this command in the root directory of you project:

npx tsc --init

This will create the tsconfig.json file for you, with a few default options turned on, along with a bunch of possible options commented out for you to see.

As you can see, this will be a JSON file. As such, the two common properties to configure here are compileOptions and include.

CompileOptions is an object where options can be set to true to change the manner in which TypeScript transpiles files into JavaScript.

For instance, in compileOptions, we can turn on noImplicitAny: true (if false, TypeScript will infer the type of “any” where no types are defined) and strictNullChecks: true (if false, Typescript will ignore “null” and “undefined”).

With these two options turned on, we can be sure our .ts files will actually check for types. If not, you could run plain JavaScript in .ts files.

The include option is a way to specify the filenames or the glob patterns to match TypeScript files. It is an array which can include specific file names or a pattern all your TypeScript files follow. A simple set up is to simply define this option as include: ["src/**/*"] (*_ matches any directory, while _ matches any file).

This would default to pick any file under the src directory with a .ts, .tsx, or .d.ts extension.

If you add allowJs: true to the compileOptions, the include defined above would also include .js and .jsx files from our src directory.

Short side note: the difference between .d.ts extension and plain .ts is that .d.ts is for declaring things to TypeScript, whereas the .ts is for TypeScript compiling down to JavaScript. The .d.ts is most commonly for declaring types in an npm module for TypeScript to know, while .ts is used for all your JavaScript files. The difference between .ts and .tsx is .tsx is used for JSX files, like React components.

Another basic option to understand is exclude. If you have a wide range in your exclude, there might be certain files you don’t want the compiler to match. For instance, including everything is one option, but then you would probably want to put node_modules under the exclude array.

See the official documentation for more about configuring your TypeScript compiler.

Once you have finished configuring your tsconfig.json file, you can add a script in your package.json to run the compiler. Under scripts, add something like, "tsc:w": "tsc -w". You can then run the script and it will point to your tsconfig.json file and compile away!

@types

If you are using a frontend framework like React, you will need to also install @types packages. Since everything in TypeScript requires type definitions, these packages like @types/react will let TypeScript know the types for all the basic React classes, functions, components, etc.

This is needed to satisfy TypeScript, and rightly so. You will also need to remember to add types to any other library added to your project (e.g. @types/react-router-dom, @types/react-bootstrap, or @types/react-redux).

Another example of the @types packages is @types/node. Without this package, keywords that we are used to, won’t be recognized in Typescript file (.ts file).

For instance, using require to import a package, would return an error like this: Error:(3, 12) TS2304:Cannot find name 'require'. The package @types/node defines this for TypeScript, so we are now allowed to use require without errors.

Some JavaScript packages already have types written in them. These won’t require installing a types packages. However, what if there aren’t types written in the packages and there isn’t a types package to install?

First of all, this is hard to find, as TypeScript has a ton of support. However, if you use TypeScript widely, you will inevitably find a JavaScript package without TypeScript support.

The easy solution here is to simply let the TypeScript complier know your module exists. This won’t add TypeScript to the package, but it will bypass the TypeScript checker and allow you to use it. There are many ways to solve this, like fully adding TypeScript to the package yourself (lots of work in most cases), but this is just a simple solve.

Create a file and name it something like this: MyModuleDesc.d.ts (the .d'ts is the important part).

In that file, simply write declare module "my-module" (with the “my-module” being the package that doesn’t have Typescript). Then, in your tsconfig.json file, under the include array, add the MyModuleDesc.d.ts file (assuming this file is in the root of your project).

React Example

Alright, so enough about the basic how-to’s, let’s actually see this migration played out in live action. I have created a simple table for data I receive from Swapi.dev:

export default function TableComponent(props) {

  const { result } = props;
  return (
    <Table>
      <Row><Cell>{result.name}</Cell></Row>
      <Row><Cell>Hair Color</Cell><Cell>{result.hairColor}</Cell></Row>
      <Row><Cell>Height</Cell><Cell>{result.height}</Cell></Row>
      <Row><Cell>Weight</Cell><Cell>{result.weight}</Cell></Row>
      <Row><Cell>Date of Birth</Cell><Cell>{result.dateOfBirth}</Cell></Row>
      <Row>
        <Cell>{result.filmNames.length <= 1 ? "Film" : "Films"}</Cell>
        <Cell>{result.filmNames.length === 0 ?
          "None" :
          result.filmNames.map((film, idx) => (
            <LineItem key={'film' + idx}>{film}</LineItem>))}
        </Cell>
      </Row>
    </Table>
  )
}

This will make for a good example. Each element we see here is a styled-component I have defined elsewhere; they are defined like this const Table = styled.div; (with a bunch of CSS defined in between the quotes).

First off, this is using absolutely no TypeScript at all in my project. The file name is TableComponent.js, and it is a child off of App.js to keep it simple. I originally initialized this project using create-react-app — and you could easily do the same. The only package you need to follow along is styled-components.

Next, to add TypeScript, you can install a few things through npm:

npm i --save typescript @types/react @types/react-dom @types/styled-components

At this level, let’s run the command in the terminal:

npx tsc --init

This will create our tsconfig.json file.

Now, you might notice an error in your tsconfig.json file. It you hover over the very top first curly brace in that file, you will see the error:

“error TS18003: No inputs were found in config file tsconfig.json. Specified include paths were ["**/*"] and exclude paths were [].”

I’ve noticed this error come and go at times, if you close out your editor and bring it back, but if you try running your app, you should see this error.

The basic idea here is we need at least one TypeScript file for the compiler to work. Let’s change the filename of TableComponent.js to TableComponent.tsx. You may need to restart your IDE, as I do while using Visual Studio Code, but you should see red lines under the props and in a few other spots, indicating an error.

We will get there, but first let’s try running it with this command:

npm run start

It will get pretty far, and you may even see the App component finish up, but our TableComponent will show an error.

More specifically, you’ll see a Typescript error: “Parameter props implicitly has an any type. TS7006″.

This is exactly what the red lines are indicating. This is because noImplicitAny is defaulted to true, and this is exactly what we want. We could go to our tsconfig.json file and change it to false. Then, we would have no errors, but basically have plain JavaScript then.

A screenshot of a codeblock in TypeScript.

One thing that’s important to note is that TypeScript ran on top of our current bundler without disturbing it.

We did an npm run start even though we didn’t appear to call any TypeScript. Technically, with the noEmit option set to true, TypeScript simply checked the types in our JSX, but our bundler emitted/output it. Another thing is to look at the tsconfig.json file — it read our app and filled in some default options for us.

Let’s also note what TypeScript did in our project: it automatically created a file in our src folder named react-app-env.d.ts. This file isn’t too important, but you’ll notice, even if you delete it, it will automatically be generated on each start/build. It is a strange TypeScript thing that simply lets TypeScript know about react-scripts types.

Without further ado, let’s work on changing this file to TypeScript.

JSX to TSX

We could easily make the red-line errors go away by adding : any to the end of our variables, but that’s basically useless.

A better way, is to do what TypeScript is made for: checking the types to avoid errors. Let’s start by addressing the variables in our map function: “film”.

You will see these have red lines under them and are frankly the easiest to understand the types they require: they’re all strings, so we can easily go through and annotate: : string on the end.

Then, we can annotate idx with : number, and it will satisfy the TypeScript compiler. All of this would be okay, but the problem remains with the props variable, which is still without annotation. And honestly, going through the props down in the JSX and annotating there isn’t the best way of doing things.

A better way is to create our own TypeScript interface or type for the props being passed in. Now, we would probably have done this in the parent App.js component normally, when we fetched the result from an API (or even better, in the API as well if we had created it), but so you can see the flexibility of TypeScript, you don’t even need to do that.

Let’s create a type for our props:

type Props = {
  result: Result,
};

Then the type for result:

type Result = {
  name: string,
  hairColor: string,
  height: string,
  weight: string,
  dateOfBirth: string,
  filmNames: string[],
};

Then, simply annotate the props: props: Props. Now, you should see the film is no longer red-underlined.

We could have made each of these interfaces instead of a new type, but as a general rule of thumb, try to make more controlled things (like props) a type. For more on choosing between type and interface, see this article.

Styled components

This is by no means a complete guide to TypeScript. However, just to see a bit more of the basics, let’s look at styled-components.

Right now we have no errors, but one of the great things about styled components is the ability to pass in a prop, so you can reuse the same definition and change it just a bit depending on the prop.

For instance, in our JSX, we want the first Cell (a styled component) to take up the entire width of our table, but the other Cells should only be half the width. So let’s put a “header” prop in there:

<Cell header> {result.name} </Cell>.

TypeScript doesn’t like this and doesn’t know what to do with it. Here’s what my current definition for Cell is:

const Cell = styled.div`
  padding: 5px 20px;
  display: flex;
  border: 1px solid black;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 50%;
`;

Normally, we could add our prop to the style we create like this:

width: ${props => props.header ? '100%' : '50%}

But we will see header here is also an error according to TypeScript, because we haven’t defined it.

We need to create a new type for Cell props, like this:

type cellProp = {
  header: boolean,
};

Now add it to the Cell component we create:

const Cell =
  styled.div <
  cellProp >
  `
        ...
        width: ${(props) => (props.header ? "100%" : "50%")};
    `;

This will clear up the errors. Then we get something else: every other instance of Cell gets an error because it doesn’t have a header prop.

This may seem strange, but it is expected behavior — we are telling TypeScript each instance of Cell needs this header boolean.

We could go through and make each instance have a prop header={false}, but that’s extra clutter in our JSX.

A better fix is to make the header prop optional. We could do that easily by adding a question mark in the type definition:

type cellProps = {
  header?: boolean,
};

Viola! No more errors.

Conclusion

This was a little taste of migrating a React component over to TypeScript. As you can see, migrating over to TypeScript is not much of a hassle at all. It is flexible, so you can use it on the files you need it for, while waiting on the files that have less priority. And before you know it, you can have TypeScript working in your project!

For more information, see the official docs.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    : Full visibility into your web apps

    LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

    In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

    .
    Stephen Hartfield Stephen is a self taught, full stack developer. He lives in St. Louis, MO with his wife and two children. He enjoys reading, writing, playing video games, playing basketball, and being a father.

    Leave a Reply