왜 내 코드는 터질까? Pure ESM 지뢰와 Dual Package의 이해
라이브러리 가져다 쓰다보면 ERR_REQUIRE_ESM 에러를 접하는 경우가 많다
왜 발생하는지 알아보자
TypeScript 컴파일 및 실행 흐름과정
- 입력(Source Code) : 개발자는 ESM 문법(
import/export)으로 코드 작성 - 변환(Compile) :
tsc(컴파일러)가 실행되면서tsconfig.json설정을 확인. 예시"module": "commonjs" - 출력(Dist Code) :
tsc(컴파일러)는node.js가 좋아하는 cjs(commonjs)문법으로 싹 바꿔서dist/등의 출력 폴더에.js파일로 저장 - 실행(Runtime) :
node.js는package.json의"type": "commonjs"설정을 보고, 변환된require코드를 문제없이 실행함
- copile :
tsconfig.json -> "module" - runtime :
package.json -> "type"
esm 으로 컴파일된 .js 를 package.json 의 type을 commonjs 로 해서 읽으면 에러 발생
에러 발생하는 예시
package.json: "type": "commonjs"
tsconfig.json: "module": "esnext"
아니 그러면 node_module에있는 .js는 esm과 cjs가 섞여있을텐대?
Pure ESM 라이브러리는 실제로 문제가 발생함(사실상 지뢰임)
일부 라이브러리들이 "우리는 더 이상 cjs 지원 안해. 오로지 esm만 사용할거야" 하고 배포를 해놓으면
문제가 터짐
- 사용자 프로젝트
package.json -> type: commonjs에서import PureESMLib from pure-esm-lib라는 esm 문법을 씀 tsconfig.json -> module: commonjs로 되어있을 경우 tsc는 문제없이 컴파일함 (결과물 :const PureESMLib = require('pure-esm-lib'))node.js가 실행하다가PureESMLib를require로 불러오려고 시도함ERR_REQUIRE_ESM에러 발생!
해결방법
애초에 라이브러리가 Dual Package 방식으로 배포되어야함
Dual Package(이중 패키지) 란 : 사용자가 CJS를 쓰든 ESM을 쓰든 알아서 맞춰주도록하는 방식
Dual Package을 한 라이브러리는 package.json 이 아래와 같음
/**
사용자의 프로젝트가 CJS(require) 방식이면 Node.js가 자동으로 "require" 필드에 적힌 파일을 가져옴.
따라서 사용자는 라이브러리가 어떻게 생겼는지 몰라도 됨
*/
{
"name": "super-library",
"main": "./dist/index.cjs.js", // CJS 사용자가 오면 이리로 안내
"module": "./dist/index.esm.js", // ESM 사용자가 오면 이리로 안내
"exports": { // Node.js 최신 버전용 표지판
"require": "./dist/index.cjs.js",
"import": "./dist/index.esm.js"
}
}3줄 요약
-
내 코드의 운명: 나는 최신 ESM(import) 문법으로 개발하지만, 결국 tsc가 **CJS(require)**로 번역(Transpile)해서 Node.js가 실행한다. (일반적인 백엔드 설정)
-
지뢰의 정체(Pure ESM): 번역된 내 코드는 require('라이브러리')를 호출하는데, 해당 라이브러리가 **"난 오직 ESM만 지원해!"**라고 선언한 경우 ERR_REQUIRE_ESM 에러가 발생한다.
-
해결사(Dual Package): 이를 방지하기 위해 라이브러리는 exports 설정을 통해 사용자가 require로 부르든 import로 부르든 알아서 **맞는 파일(.cjs/.mjs)로 안내(Routing)**해줘야 한다.