Watch 모드 구성하기

Watch 모드 구성하기 (Configuring Watch)

TypeScript 컴파일러의 watch 모드는 파일 변화를 감지해 자동으로 다시 컴파일해 주는데, 이 감지 방식을 어떻게 다듬는지가 성능에 영향을 줘요. TypeScript 3.8부터 tsconfig.json에서 직접 설정할 수 있게 됐고, 그 이전 버전의 환경 변수 방식도 여전히 쓸 수 있어요.

출처: TypeScript 핸드북 - Configuring Watch

TypeScript 3.8부터 TypeScript 컴파일러는 파일과 디렉터리를 감시(watch)하는 방식을 제어하는 설정을 노출합니다. 이 버전 이전에는 환경 변수를 사용해야 했는데, 그 방식은 여전히 사용할 수 있어요.

배경 지식 (Background)

컴파일러의 --watch 구현은 Node의 fs.watchfs.watchFile에 의존해요. 이 두 메서드는 각각 장단점이 있어요.

fs.watch는 파일 시스템 이벤트에 의존해 감시 중인 파일과 디렉터리의 변경을 알립니다. 이 커맨드의 구현은 OS에 따라 다르고 불안정해서, 많은 운영 체제에서 기대대로 동작하지 않아요. 게다가 일부 운영 체제는 동시에 존재할 수 있는 watch의 개수를 제한합니다(예: 일부 Linux 버전). 대규모 코드베이스에서 fs.watch를 많이 사용하면 이 한계를 초과해 바람직하지 않은 동작을 낳을 수 있어요. 하지만 이 구현은 이벤트 기반 모델에 의존하므로 CPU 사용은 상대적으로 가벼워요. 컴파일러는 일반적으로 디렉터리를 감시할 때 fs.watch를 사용합니다(예: 컴파일러 설정 파일에 포함된 소스 디렉터리, 모듈 해석이 실패한 디렉터리 등). TypeScript는 이를 사용해 개별 파일 watcher의 잠재적 실패를 보완하죠. 하지만 이 전략에는 핵심적인 한계가 있어요: 디렉터리의 재귀적 감시는 Windows와 macOS에서는 지원되지만, Linux에서는 지원되지 않습니다. 그래서 파일과 디렉터리 감시를 위한 추가 전략이 필요하게 됐어요.

fs.watchFile은 폴링(polling)을 사용하므로 CPU 사이클을 소모합니다. 하지만 fs.watchFile은 관심 있는 파일과 디렉터리의 이벤트를 구독하는 데 쓸 수 있는 메커니즘 중 단연코 가장 신뢰할 수 있어요. 이 전략에서 TypeScript 컴파일러는 보통 소스 파일, 설정 파일, 그리고 참조 문(statement)에 근거해 없는 것으로 보이는 파일들을 fs.watchFile로 감시합니다. 즉 fs.watchFile을 쓸 때 CPU 사용이 얼마나 높아지는가는 코드베이스에서 감시되는 파일의 수에 직접적으로 달려 있어요.

tsconfig.json으로 파일 감시 구성하기

watch 동작을 구성하는 권장 방법은 tsconfig.json의 새 watchOptions 섹션을 통하는 거예요. 아래에 예시 설정을 제공합니다. 사용 가능한 설정에 대한 자세한 설명은 다음 섹션을 참고하세요.

{
  // Some typical compiler options
  "compilerOptions": {
    "target": "es2020",
    "moduleResolution": "node"
    // ...
  },

  // NEW: Options for file/directory watching
  "watchOptions": {
    // Use native file system events for files and directories
    "watchFile": "useFsEvents",
    "watchDirectory": "useFsEvents",

    // Poll files for updates more frequently
    // when they're updated a lot.
    "fallbackPolling": "dynamicPriority",

    // Don't coalesce watch notification
    "synchronousWatchDirectory": true,

    // Finally, two additional settings for reducing the amount of possible
    // files to track  work from these directories
    "excludeDirectories": ["**/node_modules", "_build"],
    "excludeFiles": ["build/fileWhichChangesOften.ts"]
  }
}

자세한 내용은 TypeScript 3.8 릴리스 노트를 참고하세요.

환경 변수 TSC_WATCHFILE로 파일 감시 구성하기

Option Description
PriorityPollingInterval fs.watchFile을 쓰되, 소스 파일·설정 파일·없는 파일에 서로 다른 폴링 간격을 사용합니다.
DynamicPriorityPolling 자주 수정되는 파일은 짧은 간격으로, 변경되지 않은 파일은 덜 자주 폴링하는 동적 대기열(dynamic queue)을 사용합니다.
UseFsEvents fs.watch를 사용합니다. 활성 watch 개수를 제한하는 운영 체제에서는, watcher 생성에 실패하면 fs.watchFile로 폴백합니다.
UseFsEventsWithFallbackDynamicPolling fs.watch를 사용합니다. 활성 watch 개수를 제한하는 운영 체제에서는, 동적 폴링 대기열로 폴백합니다(DynamicPriorityPolling에서 설명한 대로).
UseFsEventsOnParentDirectory 포함된 파일들의 부모 디렉터리에 fs.watch를 사용합니다(순수 fs.watchFile보다 CPU 사용이 낮지만 정확성은 잠재적으로 낮아지는 절충안을 산출합니다).
default (no value specified) 환경 변수 TSC_NONPOLLING_WATCHER가 true로 설정되어 있으면 UseFsEventsOnParentDirectory를 사용합니다. 그렇지 않으면 어떤 파일에 대해서도 250ms를 타임아웃으로 삼아 fs.watchFile로 파일을 감시합니다.

환경 변수 TSC_WATCHDIRECTORY로 디렉터리 감시 구성하기

재귀적 디렉터리 감시를 기본 지원하지 않는 플랫폼(즉 macOS와 Windows가 아닌 운영 체제)의 디렉터리 감시는, TSC_WATCHDIRECTORY가 선택한 서로 다른 옵션을 사용해 각 하위 디렉터리에 대한 디렉터리 watcher를 재귀적으로 만들어 지원됩니다.

참고: 기본 재귀적 디렉터리 감시를 지원하는 플랫폼에서는 TSC_WATCHDIRECTORY의 값이 무시됩니다.

Option Description
RecursiveDirectoryUsingFsWatchFile 포함된 디렉터리와 하위 디렉터리를 감시하는 데 fs.watchFile을 사용합니다.
RecursiveDirectoryUsingDynamicPriorityPolling 동적 폴링 대기열을 사용해 포함된 디렉터리와 하위 디렉터리의 변경을 폴링합니다.
default (no value specified) 포함된 디렉터리와 하위 디렉터리를 감시하는 데 fs.watch를 사용합니다.

더 알아보기 (Learn more)