토스뱅크가 패키지를 관리하는 방식 #1: 패키지를 ESM으로만 제공하기로 했어요

1 hour ago 7

안녕하세요. 토스뱅크에서 패키지 개발 환경을 개선하고 있는 기술그룹입니다.토스뱅크에서는 여러 서비스가 함께 쓰는 코드를 패키지로 관리해요. 그동안 ECMAScript Modules(이하 ESM)와 CommonJS(이하 CJS) 두 형식으로 빌드하고, package.json의 exports로 각 환경에 맞는 코드와 타입을 제공해왔어요. 이러한 Dual package 방식의 배경과 설정은 CommonJS와 ESM에 모두 대응하는 라이브러리 개발하기: exports field에서 자세히 볼 수 있어요.그런데 Dual package 방식은 두 형식으로 빌드하는 것 외에도 신경 쓸 일이 많았어요. 같은 패키지의 상태가 둘로 나뉘거나, 한쪽 경로에서 타입을 찾지 못할 수 있었거든요. 어떤 문제인지부터 살펴볼게요.같은 패키지인데 상태가 둘로 나뉘어요React 컨텍스트를 제공하는 패키지를 ESM과 CJS로 각각 빌드했다고 해볼게요. 서비스는 ESM 구현을, 서비스가 사용하는 다른 패키지는 CJS 구현을 읽는 상황이에요.두 구현에서 각각 컨텍스트를 만들면 객체도 둘이 돼요. 서비스의 Provider는 컨텍스트 ①에 값을 넣는데, 다른 패키지의 Hook은 컨텍스트 ②에서 값을 찾을 수 있어요. Provider로 감쌌는데도 값을 읽지 못하는 거예요.같은 패키지 이름으로 불렀는데 왜 이런 일이 생길까요? exports에서 불러오는 방식에 따라 서로 다른 파일을 연결했기 때문이에요.{ "exports": { ".": { "import": "./dist/index.mjs", "require": "./dist/index.cjs" } } }이 구성에서는 import와 require()가 서로 다른 구현을 읽어요. 두 모듈이 각각 실행되면서 만든 객체는 내용이 같아도 다른 객체예요.패키지를 한 버전만 설치했어도 생길 수 있는 일이에요. 이렇게 두 모듈이 함께 로드되면서 공유해야 할 객체나 상태가 나뉘는 위험을 Dual package hazard라고 해요. 컨텍스트뿐 아니라 모듈이 보관하는 캐시나 상태도 영향을 받을 수 있어요.실행 코드는 찾았는데 타입은 찾지 못해요타입도 두 경로에서 확인해야 해요. 우리 패키지의 타입이 외부 라이브러리를 참조한다면, 그 라이브러리의 타입까지 올바르게 연결돼야 하고요.예를 들어 UI 패키지가 외부 라이브러리의 ExternalProps를 가져와 버튼의 props를 정의한다고 해볼게요.import type { E...

Read Entire Article