esModuleInterop 설정

3 minute read
2024-07-18

요약

esm 의 default 는 export const default 와 비슷하다. 다만 변수가 아닌 키워드다.


TS 의 가짜 import

타입스크립트는 기본적으로 esm 스럽게 모듈을 다룬 것처럼 보이게 한다.


esm 에서 모듈을 표준 방식으로 가져오는 키워드인 import 가 있지만, 타입스크립트에서는 컴파일 되고 나면 cjs 모듈 시스템 (require 함수 사용) 으로 바뀔 수 있다.


관련 영상도 참고하면 좋다.


CJS 모듈 가져오는 방식

요약하자면, 하나의 모듈을 하나의 객체로 묶어서 처리한다.


foo.cjs
exports.foo = () => {}
 
exports.bar = () => {}
 
exports.baz = () => {}
 
module.exports = function main() {}


이런 코드가 있다면, cjs 는 main 함수의 property 에 named-export 를 추가한다.


main.foo = () => {}
main.bar = () => {}
main.baz = () => {}


ESM 모듈 가져오는 방식

반면 esm 은 default 는 지정된 named export 이다.


{
  default : function main() {}
  
  // named-exports
  foo : () => {},
	bar : () => {},
  baz : () => {}
}


직접 겪은 사례

바벨 패키지 중 generatortraverse 가 있다. 자체적으로 타입을 제공하지 않기 때문에 @babel/__generator 와 @babel/__traverse 를 추가적으로 설치해야한다.


npm i @babel/generator @babel/traverse
npm i --save-dev @babel/__generator @babel/__traverse


하지만 문제는 여기 있다. 타입 default 선언 위치가 다른 것이었다.


기본적으로 ts 의 esModuleInterop 옵션은 false 이기 때문에 아래 임포트는 export default 를 가져온다.


import traverse from '@babel/traverse' // export default 를 가져옴


근데 해당 모듈이 cjs 로 되어 있으면 default가 없기 때문에 아래와 같이 가져오라고 한다.


solution 01
import * as traverse from '@babel/traverse'


이러면 export default 를 가져오지 않고 하나의 객체로 묶어서 cjs 처럼 가져올 수 있게 된다.


혹은 esModuleInterop 옵션을 켜준다.


solution 02
import traverse from '@babel/traverse'
// traverse === { default: ... , named-exports ... }


그래서 어쩔 수 없이 아래와 같은 이상한 코드로 해결했다.


import _traverse from '@babel/traverse'
 
const a = _traverse.default as any
const traverse = a as typeof _traverse


중간 단계에서 any 를 통해 타입 초기화 를 했다. 매우 좋지 않은 방식이지만, 다른 방법은 떠오르지 않았다.