Skip to content

Latest commit

 

History

History
325 lines (257 loc) · 6.01 KB

File metadata and controls

325 lines (257 loc) · 6.01 KB
sidebar_position 17

Migrating from JavaScript to TypeScript

This guide will help you migrate your JavaScript projects to TypeScript, following best practices and a step-by-step approach.

Preparation

  1. Install TypeScript:
npm install --save-dev typescript @types/node
  1. Create tsconfig.json:
{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "strict": false,  // Start with false, enable later
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "allowJs": true,  // Allow JavaScript files
    "checkJs": true   // Type check JavaScript files
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Step-by-Step Migration

1. Rename Files

Start by renaming your .js files to .ts or .tsx (for React components):

# Example structure
src/
├── index.js → index.ts
├── utils/
│   ├── helpers.js → helpers.ts
│   └── config.js → config.ts
└── components/
    ├── App.jsx → App.tsx
    └── Button.jsx → Button.tsx

2. Fix Import/Export Syntax

Update your imports and exports to use ES6 module syntax:

// Before
const express = require('express');
module.exports = { someFunction };

// After
import express from 'express';
export { someFunction };

3. Add Type Definitions

Start adding types gradually:

// Before (JavaScript)
function calculateTotal(items) {
  return items.reduce((total, item) => total + item.price, 0);
}

// After (TypeScript)
interface Item {
  id: string;
  price: number;
  quantity: number;
}

function calculateTotal(items: Item[]): number {
  return items.reduce((total, item) => total + item.price, 0);
}

4. Handle Third-Party Libraries

Install type definitions for your dependencies:

# Example
npm install --save-dev @types/react @types/express @types/lodash

Create custom declarations when needed:

// custom.d.ts
declare module 'untyped-module' {
  export function someFunction(): void;
  export const someValue: string;
}

5. Update React Components

Convert React components to TypeScript:

// Before (JavaScript)
const Button = ({ onClick, children }) => (
  <button onClick={onClick}>{children}</button>
);

// After (TypeScript)
interface ButtonProps {
  onClick: () => void;
  children: React.ReactNode;
}

const Button: React.FC<ButtonProps> = ({ onClick, children }) => (
  <button onClick={onClick}>{children}</button>
);

6. Handle API Responses

Add types for API responses:

interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

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

async function fetchUser(id: string): Promise<ApiResponse<User>> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

Common Migration Challenges

1. Any Type

Use any temporarily when migrating, but plan to replace it:

// Temporary
let data: any = fetchData();

// Better
interface ApiData {
  id: string;
  value: number;
}
let data: ApiData = fetchData();

2. Mixed Types

Handle cases where a value could have multiple types:

// Before
function process(value) {
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
  return String(value);
}

// After
function process(value: string | number): string {
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
  return String(value);
}

3. Class Properties

Add types to class properties and methods:

// Before
class User {
  constructor(data) {
    this.name = data.name;
    this.age = data.age;
  }
  
  getInfo() {
    return `${this.name} is ${this.age}`;
  }
}

// After
interface UserData {
  name: string;
  age: number;
}

class User {
  private name: string;
  private age: number;

  constructor(data: UserData) {
    this.name = data.name;
    this.age = data.age;
  }
  
  getInfo(): string {
    return `${this.name} is ${this.age}`;
  }
}

Gradual Migration Strategy

  1. Start with New Code

    • Write all new code in TypeScript
    • Leave existing code as JavaScript initially
  2. Enable Incremental Migration

    {
      "compilerOptions": {
        "allowJs": true,
        "checkJs": true
      }
    }
  3. Migrate File by File

    • Start with simpler files
    • Move to more complex ones
    • Update imports as you go
  4. Add Type Checking Gradually

    {
      "compilerOptions": {
        "strict": false,
        "noImplicitAny": true,    // Enable first
        "strictNullChecks": true  // Enable later
      }
    }

Testing During Migration

  1. Update Test Files
// Before
const { expect } = require('chai');

// After
import { expect } from 'chai';
import { User } from '../src/models/User';

describe('User', () => {
  it('should create user', () => {
    const user = new User({ name: 'Test', age: 30 });
    expect(user.getInfo()).to.equal('Test is 30');
  });
});
  1. Add Type Coverage Tests
npm install --save-dev type-coverage

# Add to package.json
{
  "scripts": {
    "type-coverage": "type-coverage --detail"
  }
}

Best Practices

  1. Start Small

    • Migrate one module at a time
    • Begin with self-contained utilities
  2. Use TypeScript's Configuration Options

    • Start with less strict options
    • Gradually enable stricter checks
  3. Maintain a Migration Tracker

    - [x] Utils module
    - [x] API client
    - [ ] React components
    - [ ] Tests
  4. Document Patterns

    • Create guidelines for the team
    • Document common type patterns
  5. Review and Refactor

    • Regular type safety reviews
    • Refactor as types evolve

Remember:

  1. Migration is a process, not an event
  2. Use TypeScript features gradually
  3. Keep the application working throughout
  4. Test thoroughly during migration
  5. Document decisions and patterns