Skip to Content
Course content

55: Migrating a JavaScript Project to TypeScript

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this happen a dozen times. You have a working JavaScript project, you're feeling ambitious, and you decide to "just switch" to TypeScript. You rename your files to .ts, run the compiler, and suddenly you're staring at 400 red squiggly lines. Panicked, you start adding : any everywhere just to make the errors go away. At that point, you haven't actually migrated to TypeScript—you've just written JavaScript with extra steps and a lot of frustration.

// OrderCalculator.js
export function calculateTotal(price, taxRate) {
    return price + (price * taxRate);
}

// main.js
const price = "100"; // Coming from an input field
const tax = 0.05;
console.log(`Total: ${calculateTotal(price, tax)}`); 
// Expected: 105
// Actual: "1005" (JavaScript string concatenation madness)

The Silent Failure of JavaScript Addition

In the snippet above, we have a classic JS bug. Because price is a string, the + operator decides it's doing string concatenation instead of addition. The code doesn't crash; it just gives us a wildly wrong answer. If this were a real checkout page, your customers would be very confused.

When you first move this to TypeScript, you might be tempted to just change the extension to .ts. But if you do that without a strategy, you'll find that TypeScript doesn't know if price is supposed to be a string or a number, and it might actually let this slide if your configuration is too loose, or scream at you for any types if it's too strict.

Turning on the Safety Net with tsconfig

The secret to a painless migration isn't renaming files; it's the tsconfig.json. You don't want to fix every single type error in your entire codebase in one afternoon. Instead, you want to coexist.

I always start by creating a tsconfig.json with these specific flags:

{
  "compilerOptions": {
    "allowJs": true, 
    "checkJs": false, 
    "outDir": "./dist",
    "target": "ES6",
    "module": "CommonJS",
    "strict": true
  }
}

allowJs: true is the most important part here. It tells TypeScript, "Hey, I have a mix of JS and TS files. Don't panic when you see a .js file; just let it be." I keep checkJs false initially because I don't want the compiler to report errors in my legacy JS files until I'm ready to tackle them.

The Incremental Migration Strategy

Once the project is compiling again, I use a "leaf-to-root" approach. I find the smallest, most independent utility files (the "leaves" of the dependency tree) and rename those to .ts first. In our case, OrderCalculator.js is a perfect candidate.

When I rename it to OrderCalculator.ts, I immediately define the contract:

// OrderCalculator.ts
export function calculateTotal(price: number, taxRate: number): number {
    return price + (price * taxRate);
}

Now, here is where the magic happens. When I go back to main.js (which is still JavaScript), TypeScript is still watching. If I have checkJs: true enabled for that specific file via a // @ts-check comment at the top, the editor will immediately highlight calculateTotal(price, tax) and tell me: "Argument of type 'string' is not assignable to parameter of type 'number'."

I've now found the bug without even converting main.js to TypeScript. I just have to fix the data source:

const price = parseFloat("100"); // Now it's a number

This approach prevents the "any-fest" where you just silence the compiler. You migrate the logic first, define the types, and let the compiler lead you to the bugs in the remaining JavaScript code.




📋 Practical Task

Migrating the User Session Manager

You have been handed a small legacy module that manages user sessions. It currently consists of two JavaScript files. Your task is to migrate them to TypeScript using an incremental approach.

Files provided:

// sessionUtils.js
export function formatSessionId(id) {
    return `session_${id}`;
}

// sessionManager.js
import { formatSessionId } from './sessionUtils.js';

export function createSession(user) {
    const id = Math.floor(Math.random() * 1000);
    return {
        userId: user.id,
        sessionId: formatSessionId(id),
        createdAt: new Date()
    };
}

Your Objectives:

  • Create a tsconfig.json that allows JavaScript files to exist alongside TypeScript files.
  • Rename sessionUtils.js to sessionUtils.ts and add proper type annotations for the id parameter and the return value.
  • Rename sessionManager.js to sessionManager.ts.
  • Create an interface User that ensures the user object passed to createSession has an id of type number.
  • Apply this interface to the createSession function.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.