#Frontend

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

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

Summary

CJS와 ESM 이중 빌드의 늪에서 탈출하기: 토스뱅크의 ESM-Only 전환기

Dual package hazard 해결부터 최신 Node.js 스펙 활용까지, 사내 패키지 관리 단순화 전략

토스뱅크 기술그룹에서 공통 라이브러리 운영 시 발생하던 Dual package hazard 및 타입 유실 문제를 고찰하고, ESM-only 패키지 제공 체계로 전환한 배경과 이점을 공유합니다. 통제 가능한 사내 환경의 강점과 최신 Node.js 모듈 스펙을 연계하여 개발 생산성을 끌어올린 실무 사례입니다.

  • 01ESM과 CJS 동시 지원 시 발생하는 React Context 이중화 및 상태 분리 버그 원인 분석
  • 02Type-checking 단계에서 CJS 경로의 타입 파일 누락으로 인한 any 타입 추론 위험성 지적
  • 03Node.js 22.12.0+ 별도 플래그 없는 require(esm) 지원을 활용한 ESM 전환 당위성 확보
  • 04사내 패키지 생태계 통제를 통한 빌드 포맷 단일화 및 트리 셰이킹 최적화 성과

RECOMMENDATION

모노레포나 사내 라이브러리를 구축 및 유지보수하면서 모듈 시스템 및 타입스크립트 export 설정으로 어려움을 겪는 웹 엔지니어에게 추천합니다.

The Problem

기존의 Dual package(ESM과 CJS 동시 제공) 방식은 모듈 로드 방식에 따라 동일한 패키지의 상태가 이중화되는 Dual package hazard 문제와 CJS 경로에서 타입을 찾지 못해 타입 안정성이 깨지는 문제를 유발했습니다.

The Solution

Node.js 22.12.0부터 지원되는 require(esm) 기능과 제어 가능한 사내 환경의 특성을 활용하여 기존 Dual package 빌드를 제거하고 표준 모듈 시스템인 ESM-only로 통합하였습니다.

The Result

패키지 제작자는 두 포맷의 실행 코드 및 타입을 이중 관리할 필요가 없어졌고, 사용자는 로드된 모듈 추적 및 상태 분리 오류 없이 안정적인 개발 체계를 확보했습니다.

Trade-off

구버전 Node.js 환경에 대한 하위 호환성을 포기해야 하며, CJS에서 require()로 불러오는 ESM 모듈 및 의존성에 최상위 await이 없어야 하는 실행 환경 제약이 존재합니다.

03

Key Concepts

Concept · 01

Dual Package Hazard

동일한 패키지의 ESM과 CJS 구현체가 한 애플리케이션 내에 동시 로드되어 모듈 내부의 상태나 싱글톤 객체, 캐시가 이중으로 생성되는 현상입니다.

  • React Context 패키지를 import(ESM)와 require(CJS)로 각각 불러올 때 Context 객체가 2개로 분리되어 Provider 값이 전달되지 않는 현상 모사
Concept · 02

ECMAScript Modules (ESM)

JavaScript의 공식 표준 모듈 시스템으로, import 및 export 구문을 사용하며 정적 분석을 통한 효율적인 트리 셰이킹을 지원합니다.

  • 토스뱅크 사내 패키지의 단일 빌드 표준으로 선정되어 번들 및 타입 관리 단순화에 활용
Concept · 03

exports Field

package.json 내에서 모듈의 진입점을 사용 환경(ESM/CJS, TypeScript 등) 조건에 따라 매핑하도록 지정하는 Node.js 명세입니다.

  • 기존 Dual package 구성 시 조건별 타입 경로 누락으로 ExternalProps가 any로 추론되는 문제의 배경으로 언급