개발 slecs

라이브러리의 ESM 포트로 모듈 시스템 현대화

목차

한 프로젝트에서 기존 텍스트 처리 라이브러리를 Node.js 및 ESM 환경에서 사용하려면서, humanizer.mjs 형태의 ESM 포트를 추가했다. 단순한 파일명 변경처럼 보이지만, 그 안에는 모듈 시스템 현대화와 팀 호환성 사이의 여러 선택지가 들어 있었다.

왜 ESM 포트가 필요했나

원래 humanizer는 브라우저 기반이거나 CommonJS 형식으로만 제공되고 있었다. 그런데 우리 프로젝트는 최근 서버사이드 렌더링(SSR)이나 백엔드 유틸리티 작업이 점점 늘어나면서, Node.js 환경에서 바로 사용할 수 있는 모듈 형식이 필수가 되었다.

현대 JavaScript 생태계는 ESM으로 빠르게 전환 중이다. Webpack, Vite 같은 번들러들도 ESM을 native로 다루고, npm 라이브러리 신규 배포도 ESM을 기본으로 간주하는 추세다. 레거시 CommonJS만 지원하면 나중에 "왜 이 라이브러리는 ESM이 없어?"라는 질문이 계속 나온다. 따라서 초기 단계에 포트를 해두는 게 팀 전체의 마찰을 줄인다고 생각했다.

ESM 포트 작업의 실제 내용

humanizer.mjs를 만드는 건 단순한 이름 변경이 아니다. 다음 몇 가지를 점검해야 한다:

항목 CommonJS ESM 비고
모듈 로딩 require('./util') import { fn } from './util.mjs'
글로벌 변수 __dirname, __filename import.meta.url 로 대체
동적 로드 require(variable) import() (Promise 기반)
조건부 export - package.json exports 필드

처음엔 "mjs 파일만 만들면 되겠지" 라고 가볍게 생각했는데, 실제론 호환성 테스트에 더 많은 시간이 들었다. 특히 humanizer가 외부 라이브러리나 폴리필에 의존하는 경우, Node.js 환경에서 작동하지 않을 수도 있으니까.

일반적인 전환 패턴은 이렇다:

// CommonJS (humanizer.js)
const { formatText } = require('./core');
module.exports = { humanize };

// ESM (humanizer.mjs)
import { formatText } from './core.mjs';
export { humanize };

Node.js ESM은 __dirname 이 없으므로, 파일 경로가 필요한 경우 별도 처리가 필요하다:

import { fileURLToPath } from 'url';
const __dirname = fileURLToPath(new URL('.', import.meta.url));

팀 관점: 레거시와 현대화 사이

여기서 중요한 의사결정 포인트가 있었다. 이미 작동하는 CommonJS 코드를 "그냥 두고" 새 프로젝트부터 ESM만 쓸 수도 있고, 동시에 두 형식을 모두 지원할 수도 있다.

우리가 선택한 건 후자다. 이유는:
- 팀원들이 여러 프로젝트를 동시에 진행 중이고, 모두를 한 번에 전환할 수 없음
- 기존 CommonJS 호출 코드가 깨지지 않아야 함
- 새 환경(Node/ESM)에서도 같은 라이브러리를 쓸 수 있게 하기 (중복 유지보수 방지)

이런 이중 지원은 마이그레이션 기간에 매우 유용하다. 하지만 동시에 관리 비용이 늘어난다는 걸 팀이 이해해야 한다. 두 버전의 코드가 같은 로직을 가지고 있으므로, 버그를 고칠 때 양쪽 모두 수정해야 한다. 따라서 코드 리뷰할 때 "이 로직 변경이 mjs도 반영되나?"를 꼭 체크한다.

또한 package.json의 exports 필드를 제대로 설정하지 않으면, 어떤 환경에서는 잘못된 버전을 로드할 수 있다. 이는 배포 후 휴먼 에러로 시간을 낭비하기 쉬운 부분이다.

배운 점과 다음 고민

이 작업을 하면서 느낀 게, 브라우저/Node/SSR 환경 모두에서 돌아야 하는 라이브러리는 초반부터 멀티 포맷 지원을 고려해야 한다는 것이다. 처음부터 설계할 때 환경별 진입점을 분리해두면 나중에 포팅이 훨씬 수월하다.

또 하나는, ESM 포트를 추가할 때 단순히 "mjs 파일만 만드는" 게 아니라, 팀 전체가 어느 형식을 써야 하는지 합의하는 커뮤니케이션이 먼저다. 그걸 놓치면 "어? 우리 프로젝트는 ESM을 쓰는데 왜 이건 CommonJS만 있어?" 같은 불만이 계속 나온다.

마지막으로, 나중에 CommonJS 지원을 떨어낼 때를 대비해 마이그레이션 타임라인을 문서로 남겨두면 좋다. "2년 뒤 CommonJS 버전 deprecate" 같은 공지를 미리 해두면, 팀도 준비할 시간을 갖고, 의존성을 그릴 때도 심사숙고하게 된다.


🛒 이 글과 어울리는 추천 상품

*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.

댓글 0

첫 댓글 달아줘.