CommonJS와 ESM에 모두 대응하는 라이브러리 개발하기: exports field
장호승 · 토스 · 토스 기술 블로그
소개
Node.js에는 두 가지 Module System이 존재합니다. 토스 프론트엔드 챕터에서 운영하는 100개가 넘는 라이브러리들은 그것에 어떻게 대응하고 있을까요?
AI 핵심 요약
토스 프론트엔드 챕터는 SSR을 위해 CJS를 유지하면서 브라우저 번들의 Tree-shaking을 위해 ESM도 제공해야 했고, 두 모듈 시스템의 차이 때문에 단일 형식만으로는 요구를 충족하기 어려웠습니다. 이를 해결하기 위해 package.json의 exports field를 사용해 패키지 진입점과 공개 subpath를 지정하고, require와 import 조건에 따라 각각 다른 빌드 파일을 제공했습니다. 패키지의 type 설정에 맞는 .js·.mjs·.cjs 확장자를 선택하고, TypeScript 4.7 이상에서는 조건별 .d.ts·.d.mts·.d.cts 선언 파일도 연결해야 합니다. 올바른 exports 설정은 CJS·ESM과 TypeScript 소비자를 함께 지원하며, 모듈 형식과 확장자를 맞추지 않으면 런타임 또는 타입 검사 오류가 발생한다는 점을 보여줍니다.
- CJS는 동적 모듈 로딩 때문에 정적 분석과 Tree-shaking이 어렵고, ESM은 정적인 import/export 구조로 이를 지원합니다.
- Node.js는 package.json의 type과 .cjs·.mjs 확장자로 모듈 형식을 판별하며, TypeScript 4.7의 node16·nodenext 설정도 이에 대응합니다.
- exports의 subpath를 명시하면 패키지 내부 파일의 공개 경로를 제한하고 실제 파일 위치와 import 경로를 분리할 수 있습니다.
- conditional exports에서는 패키지의 type에 맞춰 CJS에 .cjs, ESM에 .mjs 또는 .js를 지정해야 합니다.
- TypeScript 소비자까지 지원하려면 require와 import 조건별로 .d.cts·.d.mts 등 알맞은 타입 선언 파일을 제공해야 합니다.
비슷한 학습 자료
es-toolkit: How a Small Internal Library Became a Global Project
박서진/이다용 · 토스 기술 블로그
es-toolkit, 사내 작은 라이브러리가 전세계적인 라이브러리가 되기까지
박서진/이다용 · 토스 기술 블로그
97% Smaller, 2x Faster: How es-toolkit Reached 10 Million Weekly Downloads
문동욱 · 토스 기술 블로그
프론트엔드 개발에서 Next.js, 꼭 써야 할까? | EP.6 모닥불
토스 프론트엔드 챕터 · 토스 기술 블로그
Node.js 라이브러리 배포 파이프라인에 플러그인 시스템 도입기
장지훈 · 토스 기술 블로그
여러 프레임워크에서 사용할 수 있는 라이브러리 만들기
임지훈 · 토스 기술 블로그