JavaScript 프로젝트에 TypeScript 점진적으로 도입하기

배경

회사 프로젝트가 JavaScript로만 작성되어 있었는데, 코드베이스가 커지면서 타입 관련 버그가 자주 발생했다. 전체를 한 번에 마이그레이션하기엔 리스크가 커서 점진적 도입 방식을 선택했다.

설정

tsconfig.json에서 핵심은 allowJscheckJs 옵션이었다.

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": ["es2015", "dom"],
    "allowJs": true,
    "checkJs": false,
    "jsx": "react",
    "outDir": "./dist",
    "strict": false,
    "esModuleInterop": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

allowJs를 켜면 .js 파일도 컴파일 대상에 포함된다. checkJs는 꺼두고 필요한 파일만 // @ts-check 주석으로 검사했다.

진행 방식

  1. 유틸 함수부터 .ts로 변환
  2. API 응답 타입 정의 (interface 활용)
  3. 새로운 컴포넌트는 .tsx로 작성
  4. 기존 .js 파일은 수정할 때만 변환
// types/api.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

// api/user.ts
import { User } from '../types/api';

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

마주친 문제

라이브러리 타입 정의가 없는 경우가 많았다. @types/ 패키지를 찾거나, 없으면 declare module로 임시 타입을 만들었다.

// types/custom.d.ts
declare module 'old-library' {
  export function doSomething(value: string): void;
}

소감

strict 모드는 아직 적용하지 않았지만, 기본적인 타입 체크만으로도 오타나 null 참조 에러를 사전에 잡을 수 있었다. 팀원들 반응도 긍정적이라 계속 확대할 예정이다.