라이브러리의 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
첫 댓글 달아줘.