레거시 JavaScript 프로젝트에 TypeScript 점진적으로 도입하기
배경
팀 내에서 타입 관련 버그가 자주 발생하면서 TypeScript 도입을 논의하게 되었다. 하지만 2년간 작성된 JavaScript 코드를 한 번에 전환하는 것은 현실적으로 불가능했다. 점진적 마이그레이션 전략을 세우고 실행에 옮겼다.
설정
먼저 tsconfig.json을 추가하고 allowJs 옵션을 활성화했다. 이렇게 하면 .js와 .ts 파일이 공존할 수 있다.
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"allowJs": true,
"checkJs": false,
"jsx": "react",
"strict": false,
"esModuleInterop": true
}
}
처음부터 strict 모드를 켜면 마이그레이션이 너무 힘들어서 일단 꺼두었다. 나중에 점진적으로 켤 계획이다.
마이그레이션 순서
- 유틸리티 함수부터 시작
- 타입 정의가 명확한 모듈
- 새로 작성하는 코드는 무조건 TypeScript
처음에는 utils 디렉토리의 순수 함수들부터 .ts로 변환했다.
// before: utils.js
export function formatDate(date) {
return date.toISOString().split('T')[0];
}
// after: utils.ts
export function formatDate(date: Date): string {
return date.toISOString().split('T')[0];
}
마주친 문제들
외부 라이브러리 타입 정의 부족
사용 중인 일부 npm 패키지에 @types가 없었다. 일단 declare module로 임시 처리했다.
declare module 'legacy-library' {
export function doSomething(param: any): any;
}
암묵적 any 타입
레거시 코드를 변환하다 보면 타입을 정확히 알 수 없는 경우가 많았다. 처음에는 any를 사용하고 주석으로 TODO를 남겨두었다.
빌드 시간 증가
TypeScript 컴파일이 추가되면서 빌드 시간이 약 30% 증가했다. ts-loader 대신 babel-loader에 @babel/preset-typescript를 사용하는 방식으로 변경해서 어느 정도 개선했다.
소감
아직 20% 정도만 변환했지만, 이미 효과를 체감하고 있다. IDE에서 자동완성이 잘 작동하고, 리팩토링할 때 실수가 줄었다. 점진적 마이그레이션이 정답이었다.