TypeScript interop

import 문법보다 세 설정의 방향을 먼저 맞춘다

CommonJS 호환 문제는 소스 문법 하나로 끝나지 않습니다. TypeScript가 어떤 파일을 내보내는지, Node가 그 파일을 어떤 모듈로 읽는지, 타입 선언이 실제 값 모양을 어떻게 설명하는지를 함께 봅니다.

first checks
tsconfig module, esModuleInterop
package.json type, 실행 파일 확장자
types .d.ts, @types, declare module

같은 방향이어야 하는 세 축

컴파일 출력

module 옵션

CommonJSrequire 계열 출력, ESNextNodeNext면 ESM 출력에 가깝습니다.

런타임 해석

Node가 읽는 방식

"type": "module", .mjs, .cjs가 배포 파일의 모듈 의미를 결정합니다.

타입 표면

가져오기 모양

default, named import, namespace import가 실제 module.exports 모양과 맞아야 합니다.

상황별로 맞춰야 하는 조합

상황
권장 조합
사용 코드
깨질 때 보이는 신호
기존 CommonJS 패키지
module: "CommonJS" esModuleInterop: true
import pkg from "pkg"
default가 undefined가 되거나 함수가 아니라고 나옴
객체 형태 export
타입 선언이 named member를 정확히 공개해야 함
import { add } from "./util"
타입은 통과했지만 런타임 값에 멤버가 없음
Node ESM 프로젝트
module: "NodeNext" "type": "module"
import { value } from "./file.js"
Node가 import를 CommonJS 파일로 해석하거나 확장자를 못 찾음
타입 없는 JS 모듈
@types 설치 또는 declare module 작성
import lib from "legacy-lib"
모든 값이 any로 퍼져 상호 운용 오류가 늦게 발견됨

실제 프로젝트에서 확인하는 순서

01 소스 import 고정 default, named, namespace 중 하나로 팀 규칙을 정합니다.
02 dist 출력 확인 exports, require, import 중 무엇이 남는지 봅니다.
03 Node 실행 규칙 확인 배포 환경의 type과 확장자가 출력 형식과 맞는지 봅니다.
04 타입 선언 보강 호환을 any로 덮지 말고 선언 파일로 값 모양을 고정합니다.