Convert Existing JavaScript to TypeScript Project

TypeScript is a superset of JavaScript that adds optional static typing and other features to help improve developer productivity, code maintainability, and code quality. Converting an existing JavaScript project to TypeScript can be a great way to take advantage of these benefits, but it can be challenging without proper guidance.

In this lesson, we will walk through the process of converting an existing JavaScript project to TypeScript, step by step. We will start with a simple JavaScript project and gradually add TypeScript features, explaining each step along the way.

Step 1: Install TypeScript

The first step is to install TypeScript, which can be done using npm (Node Package Manager). Assuming you already have Node.js installed on your system, run the following command in your project directory:

npm install typescript --save-dev

This will install the latest version of TypeScript as a development dependency of your project.

Step 2: Configure TypeScript

Next, we need to configure TypeScript to compile our code. Create a tsconfig.json file in the root of your project directory with the following contents:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}

Let’s take a closer look at each of these options:

  • target: Specifies the version of ECMAScript that the compiled code should target. In this case, we’re targeting ECMAScript 5, which is compatible with most modern browsers.
  • module: Specifies the module system that the compiled code should use. CommonJS is the default option and works well with Node.js.
  • strict: Enables strict type-checking options that help catch common programming errors.
  • esModuleInterop: Allows default imports from modules with no default export, which is a common pattern in JavaScript code.
  • forceConsistentCasingInFileNames: Ensures that file names match the casing of their imports, which can help avoid issues when deploying to case-sensitive file systems.

The include and exclude options specify which files should be compiled by TypeScript. In this case, we’re including all .ts files in the src directory and excluding any files in the node_modules directory or ending in .spec.ts, which are typically used for testing.

Step 3: Convert JavaScript Files to TypeScript

Now that TypeScript is installed and configured, we can start converting our JavaScript files to TypeScript. Let’s start with a simple hello.js file:

function sayHello(name) {
  console.log(`Hello, ${name}!`);
}

sayHello('World');

To convert this file to TypeScript, rename it to hello.ts and add type annotations to the sayHello function:

function sayHello(name: string) {
  console.log(`Hello, ${name}!`);
}

sayHello('World');

The : string after the name parameter indicates that name should be a string type. TypeScript will now check that any calls to sayHello provide a string argument, which can help catch errors early.

Step 4: Use Type Annotations

As we continue to convert our code to TypeScript, we should add type annotations wherever possible. Here’s an example of a JavaScript function that takes an object parameter:

interface Person {
  name: string;
  age: number;
}

function printPerson(person: Person) {
  console.log(`Name: ${person.name}, Age: ${person.age}`);
}

printPerson({ name: 'Alice', age: 30 });

In this example, we’ve defined an interface called Person that describes the shape of an object with a name property of type string and an age property of type number. We’ve then updated the printPerson function to take a Person parameter instead of an untyped person parameter.

By using an interface to describe the shape of the Person object, we can ensure that any objects passed to printPerson have the required properties with the correct types. This can help prevent bugs caused by passing in objects with incorrect properties or types.

Step 5: Use Union Types and Type Aliases

Union types and type aliases are powerful features of TypeScript that can help make your code more expressive and readable.

Here’s an example of a JavaScript function that takes an array of either strings or numbers:

function printArray(arr) {
  for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
  }
}

printArray(['a', 'b', 'c']);
printArray([1, 2, 3]);

To convert this to TypeScript, we can use a union type to describe the array parameter:

function printArray(arr: (string | number)[]) {
  for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
  }
}

printArray(['a', 'b', 'c']);
printArray([1, 2, 3]);

In this example, we’ve added a union type (string | number)[] to the arr parameter, indicating that it should be an array containing either strings or numbers.

We can also use a type alias to make the code more readable:

type StringOrNumberArray = (string | number)[];

function printArray(arr: StringOrNumberArray) {
  for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
  }
}

printArray(['a', 'b', 'c']);
printArray([1, 2, 3]);

Here, we’ve defined a type alias StringOrNumberArray that represents an array containing either strings or numbers. We’ve then used this type alias as the type annotation for the arr parameter.

Using union types and type aliases can make your code more self-documenting and easier to read, especially when working with complex data structures.

Step 6: Use Non-Nullable Types and Optional Properties

In JavaScript, properties of an object can be undefined or null, which can lead to bugs if not handled properly. TypeScript provides non-nullable types and optional properties to help prevent these bugs.

Here’s an example of a JavaScript function that takes an object with an optional email property:

function sendEmail(user) {
  if (user.email) {
    console.log(`Sending email to ${user.email}...`);
  } else {
    console.log(`No email address found for user ${user.name}.`);
  }
}

sendEmail({ name: 'Alice' });
sendEmail({ name: 'Bob', email: 'bob@example.com' });

To convert this to TypeScript, we can use an optional property and a non-nullable type:

interface User {
  name: string;
  email?: string;
}

function sendEmail(user: User) {
  if (user.email) {
    console.log(`Sending email to ${user.email}...`);
  } else {
    console.log(`No email address found for user ${user.name}.`);
  }
}

sendEmail({ name: 'Alice' });
sendEmail({ name: 'Bob', email: 'bob@example.com' });
sendEmail({ name: 'Charlie', email: null }); // error: 'null' is not assignable to type 'string | undefined'

In this example, we’ve defined an interface called User that has a required name property and an optional email property. We’ve also updated the sendEmail function to take a User parameter instead of an untyped user parameter.

By using an optional property with the email?: string syntax, we indicate that the email property may be undefined. This is useful for cases where the email property is optional, but we still want to be able to access it if it’s present.

To prevent null values from being assigned to the email property, we can use the non-nullable type string | undefined instead of just string. This ensures that the email property can only be assigned a value that is either a string or undefined.

Using non-nullable types and optional properties can help catch bugs caused by null or undefined values, and make your code more robust and reliable.

Conclusion

In this lesson, we’ve covered the basics of converting an existing JavaScript project to TypeScript. We started by installing the TypeScript compiler and adding type annotations to our code. We then used advanced features of TypeScript such as interfaces, union types, type aliases, non-nullable types, and optional properties to make our code more expressive, readable, and robust.

By converting your existing JavaScript projects to TypeScript, you can catch bugs earlier, make your code more maintainable, and improve collaboration with other developers. TypeScript is a powerful tool that can help you write better code, and I hope this lesson has given you a good starting point for using it in your own projects.