회사에서 프론트엔드 v2 개발을 열심히 진행하던 중, 타입스크립트 컴파일러가 멈춰버리는 현상이 발생하였습니다.

평소와 같았으면 5초면 끝날 컴파일이 몇 분이 지나도 끝나지 않았습니다.

$ ddongs > yarn type
 
yarn run v1.22.22
$ tsc --noEmit
 
# ... 멈춰버림 ㅇㅅㅇ..
 

처음 겪어보는 이슈였고 프로젝트 빌드 전에 타입스크립트 컴파일은 어플리케이션 런타임 에러를 걸러내는데에 있어서 매우 중요한 사항이였기 때문에 필연적으로 잡고가야하는 이슈였습니다.

첫번째로,

마지막 컴파일 시점을 생각해봅니다.

현재 날짜 25/09/06 기준 25/08/14 가 최종 컴파일 및 빌드 시점입니다.

그 이후로 개발된 기능들의 페이지에 종속된 기능들을 위주로 먼저 순환 참조가 될 만한 타입 관련 코드들을 체크해보기 시작했습니다.

그러던 와중,, 아래와 같은 코드를 발견하였습니다.

export interface RevisionListResponseProps extends ResponseModel {
  responseObject: RevisionListResponseProps[]
}

????????????????

자기 자신을 이상하게 참조하고 있는 위의 타입 인터페이스 코드를 수정하였습니다.. 회사 가서 옆자리 꿀밤 좀 먹여야겠습니다.

그리고 수정사항은 더 이상 보이지 않아 다시 한 번 컴파일을 해보았습니다.

yarn run v1.22.22
 
$ tsc --noEmit

분명 위에서 언급 했던 코드 때문일거라고 확신했지만, 코드를 수정한 뒤에도 제 예상과 다르게 1분 이상 컴파일이 지속되었습니다..

이 때 한 가지 생각이 들었습니다.

타입스크립트 컴파일 과정을 로그화 시켜서 볼 순 없을까..?

컴파일 과정 로그 만들기

Typescript 공식문서에서

Reference

--generateTrace 라는 cli 옵션을 찾을 수 있었습니다.

package.json 파일 script 블록에 아래와 같은 script를 추가해주었습니다.

{
  "type:trace": "tsc --noEmit --generateTrace ./trace"
}

위 스크립트는, 컴파일 시, 별도의 d.tsjs 파일을 배출하지 않고 타입체크만 하며, ./trace 경로에 trace.json 파일을 생성하겠다. 라는 의미입니다.

그리고 스크립트를 실행해주겠습니다. 최대 몇 분까지 진행되는지 보기 위해 프로세스를 kill 하지 않고 진행해보겠습니다.

$ yarn type:trace

./trace 경로에 trace.json 이라는 파일을 생기는 것을 확인할 수 있습니다.

[
  {
    "name": "process_name",
    "args": { "name": "tsc" },
    "cat": "__metadata",
    "ph": "M",
    "ts": 95416.79215431213,
    "pid": 1,
    "tid": 1
  },
 
  {
    "name": "thread_name",
    "args": { "name": "Main" },
    "cat": "__metadata",
    "ph": "M",
    "ts": 95416.79215431213,
    "pid": 1,
    "tid": 1
  },
 
  {
    "name": "TracingStartedInBrowser",
    "cat": "disabled-by-default-devtools.timeline",
    "ph": "M",
    "ts": 95416.79215431213,
    "pid": 1,
    "tid": 1
  },
 
  {
    "pid": 1,
    "tid": 1,
    "ph": "B",
    "cat": "program",
    "ts": 95960.79206466675,
    "name": "createProgram",
    "args": { "configFilePath": "/Users/ddongs/Desktop/webconsole_view_v2/tsconfig.json" }
  },
 
  {
    "pid": 1,
    "tid": 1,
    "ph": "B",
    "cat": "parse",
    "ts": 96923.08306694031,
    "name": "createSourceFile",
    "args": { "path": "/Users/ddongs/Desktop/webconsole_view_v2/src/App.tsx" }
  }
 
  //...
]

trace.json 파일의 형식은 위와 같았습니다.

실시간으로 컴파일이 진행되는 과정을 확인하던 중 아래와 같은 형식의 로그가 무한루프로 15분 이상 찍히는 것을 볼 수 있었습니다.

{"pid":1,"tid":1,"ph":"I","cat":"checkTypes","ts":15857198.208,"name":"instantiateType_DepthLimit","s":"g","args":{"typeId":414150,"instantiationDepth":100,"instantiationCount":1239908}},
 
{"pid":1,"tid":1,"ph":"I","cat":"checkTypes","ts":15857205.75,"name":"instantiateType_DepthLimit","s":"g","args":{"typeId":414150,"instantiationDepth":100,"instantiationCount":1239912}},
 
{"pid":1,"tid":1,"ph":"I","cat":"checkTypes","ts":15857212.917000001,"name":"instantiateType_DepthLimit","s":"g","args":{"typeId":414150,"instantiationDepth":100,"instantiationCount":1239915}},
 
{"pid":1,"tid":1,"ph":"I","cat":"checkTypes","ts":15857220.25,"name":"instantiateType_DepthLimit","s":"g","args":{"typeId":414150,"instantiationDepth":100,"instantiationCount":1239919}},
 
{"pid":1,"tid":1,"ph":"I","cat":"checkTypes","ts":15857227.625,"name":"instantiateType_DepthLimit","s":"g","args":{"typeId":414150,"instantiationDepth":100,"instantiationCount":1239922}}
//..

그리고 이 무한루프의 끝나는 지점을 확인해보니..

{
  "pid": 1,
  "tid": 1,
  "ph": "X",
  "cat": "check",
  "ts": 5278836.75,
  "name": "checkExpression",
  "dur": 954825923.708,
  "args": {
    "kind": 238,
    "pos": 3074,
    "end": 3257,
    "path": "/users/ddongs/desktop/webconsole_view_v2/src/pages/management/database/components/form/databaseeditableform.tsx"
  }
}

위의 부분이였습니다. AI의 힘을 빌려 위의 로그는

/users/ddongs/desktop/webconsole_view_v2/src/pages/management/database/components/form/databaseeditableform.tsx 의 경로의 파일의 3074 ~ 3257 사이의 문자 인덱스에서 15분 이상 타입 체크 프로세스가 진행되었다. 라는 뜻이라는 것을 알 수 있었습니다.

이로써, 문제가 되는 타입 컴파일 지점을 확인해본 결과,

const triggerFields = [
  "jdbcUrlInfo",
  "poolInfo.driverClassName",
  "poolInfo.jdbcUrl",
  "poolInfo.password",
  "poolInfo.username",
] satisfies Path<typeof method.formState>

라는 부분임을 확인할 수 있었습니다.

react-hook-form의 useForm을 별도의 제네릭 타입인자를 받는 다국어 처리용 useTranslationForm 이라는 커스텀 훅으로 사용하고 있었는데, 복잡한 useForm의 formState의 타입을 추론하는데 있어서 어디선가 컴파일이 꼬인 것 같았습니다.

const triggerFields = [
  "jdbcUrlInfo",
  "poolInfo.driverClassName",
  "poolInfo.jdbcUrl",
  "poolInfo.password",
  "poolInfo.username",
] as const

특정 필드의 유효성 검사를 위한 배열 상수이기 때문에 일단은 as const 키워드로 타입 단언을 시켜주었습니다.

그 결과 타입스크립트 컴파일이 평소처럼 5초만에 끝나는 것을 확인할 수 있었습니다!!!

🥹 이거 때문에 3일을 삽질한 것 같습니다..

이번 일을 계기로, 다시 한 번 타입스크립트 컴파일 에러가 났을 때, 해결하는데에 있어서 접근 방향을 보다 더 잘 잡을 수 있을 것만 같았습니다.

프로젝트 타입스크립트 컴파일에 고생하는 분들이 조금이나마 이 글을 보고 도움이 되셨으면 좋겠다는 마음으로 포스팅을 마무리하겠습니다.