왜 만들었나
문자열 처리, 객체 조작, 쿠키, 숫자 연산, 유효성 검사, 포맷팅, 디바이스 감지. 프로젝트마다 비슷한 유틸 함수를 다시 쓰고 있었습니다. 이걸 TypeScript 라이브러리로 묶어 npm에 배포했습니다.
혼자 쓰던 코드를 남이 쓰게 되자 기준이 달라졌습니다. "동작한다"로는 부족하고, "이번 변경이 기존 사용자를 깨뜨리지 않는다"를 증명해야 했습니다.
구조
stringUtil,objectUtil,numberUtil처럼 기능별 독립 모듈로 나눠 트리 셰이킹이 되게 했습니다. 쓰지 않는 함수는 번들에 들어가지 않습니다.- 네임스페이스 방식(
stringUtil.escapeHtml)과 개별 함수 import 방식을 모두 지원합니다. - 제네릭과 타입 가드로 반환 타입을 보존해, 호출하는 쪽에서 타입을 다시 단언할 필요가 없게 했습니다.
환경에 붙은 함수 다루기
브라우저 API에 의존하는 함수가 가장 까다로웠습니다.
- SSR: storage·디바이스 감지 유틸은
typeof window === 'undefined'일 때 에러 없이 안전한 기본값을 돌려줍니다. - 클립보드: 최신 Clipboard API를 먼저 쓰고, 실패하면 레거시
execCommand방식으로 자동 전환합니다.
이 두 경로를 모두 테스트하려면 전역을 바꿔 끼워야 해서 vi.stubGlobal로 모킹했고, sleep 같은 타이머 함수는 vi.useFakeTimers()로 실제 대기 없이 타이밍을 검증했습니다.
배포와 버전
타입 검사와 테스트는 CI에서 자동으로 돌고, 태그를 푸시하면 GitHub Actions가 빌드해 npm에 배포합니다. 버전은 X.Y.Z로 관리하고, 무엇이 하위 호환을 깨는 변경인지 기준을 세워 메이저·마이너·패치를 나눕니다.
Vitest 커버리지는 약 90%입니다. 이 경험은 테스트를 설계 문제로 다루기에 더 적었습니다.