토스뱅크가 10월 16일 공용 패키지의 기준을 안정성, 예측 가능성, 디버깅 편의성으로 정리한 글을 공개했다. 앞선 ESM 단일 배포 선택에 이어 패키지를 만드는 팀과 실제 서비스를 만드는 팀의 책임을 나눈 후속 기록이다. 사용하는 서비스는 각자 지원하는 브라우저와 런타임이 다르다. 패키지 팀은 이런 환경에서 동작 가능한 코드를 전달하고, 최종 서비스는 자신의 대상에 맞게 문법 변환과 번들링을 수행한다. 특정 빌드 도구의 속도 순위보다 어떤 변환을 어느 단계에서 맡길지 정한 사례라는 점에서 읽을 만하다.
타입 검사와 폴리필로 끝나지 않는 호환성
원문은 문법을 변환하는 도구의 지원 범위와 실행 중 사용하는 API의 제공 여부를 구분한다. 낮은 타깃을 지정해도 변환기가 구현하지 못한 문법까지 처리되는 것은 아니며, 폴리필도 모든 엔진 기능을 완전히 재현할 수는 없다는 설명이다. 호스트가 나중에 트랜스파일한다고 해서 패키지 개발 시점의 검토가 사라지는 것도 아니다. 토스뱅크는 API 지원 범위를 타입 검사에 반영하면서 실제 API와 문법 변환 가능성도 별도로 확인한다고 밝혔다. 코드가 타입 검사를 통과했다는 사실과 대상 기기에서 실행 가능하다는 판단이 서로 다른 이유다.
모듈 경계는 프레임워크 동작의 일부
패키지를 미리 하나로 묶으면 파일별 지시문의 적용 범위가 바뀔 수 있다. 토스뱅크는 클라이언트 컴포넌트와 일반 유틸리티가 섞이는 예를 들어 원래 파일 구조를 보존하기로 했다고 설명한다. 넥스트 공식 문서에서 use client는 서버와 클라이언트의 경계를 선언하는 지시문이다. 단순한 주석처럼 다루면 안 된다. 원본의 분리된 구조를 유지하는 선택은 최종 앱이 이 경계를 해석할 정보를 남긴다. 다만 모든 패키지가 번들링을 피해야 한다는 보편 규칙을 제시한 것은 아니며 해당 팀이 사용 환경에 맞춰 선택한 배포 기준이다.
원본 구현으로 돌아가는 선언 소스맵
디버깅을 위해서는 선언 소스맵과 원본 파일을 설치된 패키지에 포함한다. 타입스크립트 공식 설명에 따르면 declarationMap은 타입 선언 파일의 위치를 원래 타입스크립트 소스의 위치에 연결한다. 편집기의 정의 이동에서 선언만 보는 대신 실제 구현을 찾도록 돕는 기능이다. 이것은 런타임 예외를 원본 코드에 매핑하는 일반적인 자바스크립트 소스맵과 목적이 다르다. 패키지를 쓰는 개발자가 옵션을 생략했을 때의 동작이나 오류 조건을 살필 수 있게 한다는 점에서 문서와 타입을 보완하지만, 설명 문서를 전부 대신하지는 않는다.
ES5 출력에도 Proxy 생성자는 그대로 남았다
호환성의 층위를 구분하기 위해 TypeScript 5.9.3으로 작은 파일을 직접 컴파일했다. target은 ES5, module은 ESNext, lib는 ES2015로 두고 optional chaining을 쓰는 함수와 new Proxy({}, {})를 함께 넣었다. 타입 검사와 출력은 성공했다. 출력에서는 user?.name이 null·undefined를 확인하는 조건식으로 변했지만 new Proxy({}, {})는 그대로 남았다. 낮은 target이 문법을 변환하는 일과 실행 환경에 API를 만들어 주는 일이 다르다는 것을 한 출력에서 확인할 수 있었다.
같은 실행에서 declaration과 declarationMap을 켜자 .d.ts와 .d.ts.map이 생성됐고 맵의 sources는 ../src/example.ts를 가리켰다. 그 경로에 실제 원본 파일이 존재하는 것도 확인했다. 파일 맨 앞의 use client 지시문은 단일 파일 출력에 유지됐다. 다만 이것은 컴파일 결과의 확인이다. Next.js가 번들 전체를 어떻게 나누는지, 소비 앱의 IDE가 배포 패키지에서 정의 이동을 완성하는지, 구형 브라우저가 실행하는지는 이번 시험에 포함하지 않았다.
이 결과를 배포 검토에 옮기면 질문이 구체화된다. 문법이 변환됐는지, 필요한 런타임 API가 있는지, 프레임워크 지시문이 어느 모듈에 적용되는지, 선언 맵이 설치된 원본까지 이어지는지를 따로 확인할 수 있다. 타입 검사 성공을 실행 가능성 전체로 읽으면 Proxy 같은 잔여 의존성을 놓칠 수 있고, 소스맵 생성만 확인하면 실제 배포에서 빠진 원본 파일을 놓칠 수 있다.
| 항목 | 관측 결과 | 추가로 확인할 범위 |
|---|---|---|
| optional chaining | ES5 조건식으로 변환 | 대상 엔진에서 앱 전체 실행 |
| Proxy 생성자 | new Proxy 호출 유지 | 대상 엔진의 API 지원 |
| use client | 단일 파일 선두에 유지 | 소비 앱 번들에서의 모듈 경계 |
| 선언 소스맵 | ../src/example.ts 연결 및 파일 존재 | 배포 패키지 포함 여부와 소비 IDE 탐색 |
같은 ES5 출력에서 달라진 문법과 남은 APIJAVASCRIPT
'use client';
export function readName(user) {
return user === null || user === void 0 ? void 0 : user.name;
}
export var proxy = new Proxy({}, {});공용 패키지의 기준은 배포할 모듈과 사용 서비스가 맡을 변환·번들링을 나눴다. 타입 검사와 실제 런타임 API, 프레임워크 경계를 구분해 호환성을 확인하고 원본을 추적할 정보를 보존해야 한다.
ARCH.B 의견
이 접근을 참고하는 팀은 저장소 안의 소스 테스트와 실제로 배포한 패키지를 사용하는 검사를 구분할 필요가 있다. 압축된 배포물을 작은 서비스에 설치하고 서버·클라이언트 각각에서 필요한 함수를 불러온 뒤 지원 대상 환경에서 실행해 볼 수 있다. 이 과정에서 원래 포함돼야 할 파일이 빠지거나 지시문이 달라지는 문제를 발견할 수 있다. 패키지 내부에서 잘 동작하는 코드라도 사용하는 앱의 해석 조건이 다르면 결과가 달라질 수 있기 때문이다. 검사의 중심을 빌드 성공 여부에서 사용처의 동작으로 옮기는 제안이다.
선언 소스맵이 원본을 가리키더라도 배포 파일에 그 경로의 소스가 없으면 기대한 탐색이 이어지지 않는다. 버전을 올릴 때 선언과 원본이 같은 변경을 담았는지, 배포에 필요 없는 내부 파일이 섞이지 않았는지 함께 확인하는 편이 좋다. 최종 변환을 호스트에 맡기는 구조에서는 패키지와 앱의 지원 범위를 공동 문서로 남길 필요도 있다. 누가 어떤 문법을 처리하고 누가 API를 제공하는지 기록하면 문제가 발생했을 때 책임을 추측하는 시간을 줄일 수 있다. 이 사례의 장점은 도구를 하나 더 추가한 데보다 이런 판단 기준을 명시했다는 데 있다.
소비 앱에서 정의 이동을 직접 실행해 보는 작은 확인도 배포 검토에 넣을 수 있다. 파일이 존재하는지와 편집기가 그 파일까지 연결하는지는 다른 문제이기 때문이다.


.png&w=1200&q=75)