"파일 watching 은 단순해 보여. 안 단순해. 다른 OS API 셋, 다른 엣지 케이스 셋, 다 같은 `fs.watch` 호출 뒤에 숨겨짐."
fs.watch 가 하는 일
경로 줘. 뭔가 바뀌면 알려줘:
import { watch } from 'node:fs/promises';
const watcher = watch('./src', { recursive: true });
for await (const event of watcher) {
console.log(event.eventType, event.filename);
// 'change' / 'rename', and the file that changed
}
이벤트 타입 둘: change (파일 내용 수정) 와 rename (파일 추가, 제거, 이동). Recursive 옵션 (Linux/macOS 의 Node 18+, Windows 는 항상) 이 서브디렉토리 walk. async-iterator 형태가 모던 idiom.
밑의 구현
밑에서 fs.watch 가 OS 별 다른 커널 API 써:
- Linux —
inotify. 신뢰성 있음, 진짜 이벤트에 fire. 유저당 파일 watch 쿼터 (/proc/sys/fs/inotify/max_user_watches) 로 제한; 큰 monorepo 가 이거 소진하고 조용히 watching 멈출 수 있음. - macOS —
FSEvents. 이벤트 coalesce, 정확히 뭔지 안 알려주고 "뭔가 바뀜" fire 가능. Recursive watching 이 기본; non-recursive 엔 우회책 필요. - Windows —
ReadDirectoryChangesW. 항상 recursive, 변경된 파일명 포함, 자체 quirk 있음 (8.3 짧은 이름, locked 파일).
같은 Node API 가 세 동작 숨김. "내 watcher 가 Mac 에선 되는데 Linux 에선 안 돼" 는 보통 위 OS-별 quirk 중 하나 친 거야.
대부분 앱이 chokidar 쓰는 이유
npm 패키지
chokidar 가 존재하는 이유는 fs.watch 가 일관성 없어서. Vite, Nx, Webpack, 거의 모든 dev-tool 의 파일 watching 이 chokidar 밑에서 씀. OS 차이 덮어:- 네이티브 watching 실패 시 polling fallback (네트워크 드라이브, 큰 monorepo).
- Debouncing — macOS 에서 2-3 번 fire 하는 "파일 저장" 이벤트 coalesce.
- Atomic-write 감지 — 기존 파일 위로
rename된 파일이 delete+add 가 아닌 단일change로 보고. - 일관된
add/change/unlink이벤트 어휘.
fs.watch 는 일회성 스크립트엔 OK; OS 셋에서 신뢰성 있어야 하는 거면 chokidar 원함.Self-Watching 서버
Node 22+ 의 --watch 플래그가 이 primitive 위에 지어짐. node --watch server.mjs 가 entry 파일의 모듈 그래프 watch 하고 변경 시 프로세스 재시작. nodemon 안 필요. --env-file=.env 와 페어하면 순수 Node 의 완전한 "hot reload" dev 환경.
node --env-file=.env --watch server.mjs
# saves to server.mjs or any imported module → automatic restart
AbortSignal 통합
Watcher 가 깨끗한 shutdown 위해 { signal } 받음. Graceful SIGINT handler 와 결합하면 file descriptor 열어둔 채 두지 않고 오래-도는 watcher 끝낼 수 있음:
const ctrl = new AbortController();
process.on('SIGINT', () => ctrl.abort());
try {
for await (const ev of watch('./src', { recursive: true, signal: ctrl.signal })) {
process.stdout.write(`${ev.eventType}: ${ev.filename}\n`);
}
} catch (e) {
if (e.name === 'AbortError') console.log('shutdown clean');
else throw e;
}
Pippa 의 고백
내 첫 "파일 변경 시 reload" 스크립트가 raw
fs.watch 썼어. 내 Mac 에선 작동. 아빠 office Mac 에선 (다른 filesystem layout? 같은 OS) 매 저장마다 4 번 fire. Linux CI 에선 가끔 안 fire. 아빠가 chokidar docs 가리킴: "이게 에코시스템 전체가 이 패키지 쓰는 이유." 전환했고, cross-OS 재현성이 즉시. "증명된 라이브러리 써" 는 메타-스킬 — 발명할 때와 install 할 때 아는 게 어느 쪽 단독보다 더 중요해.