빠른 설정
{
"compilerOptions": {
"module": "ESNext",
"verbatimModuleSyntax": true
}
}import { createUser, type User } from "./user";
import type { UserInput } from "./user-input";
export { createUser };
export type { User, UserInput };| 상황 | 먼저 볼 기준 |
|---|---|
| 타입 전용 import가 JS에 남으면 안 된다 | import type 사용 |
| side effect import가 사라지면 안 된다 | 값 import나 bare import로 표현 |
| bundler, swc, esbuild를 쓴다 | emit 규칙을 명시적으로 유지 |
| ESM/CJS가 섞인다 | module 설정과 package.json type 확인 |
무엇이 바뀌나
TypeScript는 기본적으로 타입 분석에만 쓰인 import를 JS 출력에서 지울 수 있습니다. 이를 import elision이라고 부릅니다. 문제는 어떤 import가 사라지고 어떤 import가 남는지, 그리고 side effect가 있는 모듈이 의도치 않게 빠지는지 판단하기 어려운 경우가 있다는 점입니다.
import { User } from "./types";
export function greet(user: User) {
return `Hello ${user.name}`;
}verbatimModuleSyntax를 켜면 규칙이 더 직접적입니다. type modifier가 붙은 import/export는 JS 출력에서 제거되고, 붙지 않은 import/export는 그대로 남는 방향으로 해석됩니다. 즉 "남길 값 의존성"과 "지울 타입 의존성"을 코드에 직접 표시해야 합니다.
import { run } from "./runtime";
import type { RuntimeConfig } from "./runtime";
run();type-only import
타입만 필요하면 import type을 사용합니다. 값으로 쓰는 import와 타입으로만 쓰는 import가 분리되면 순환 의존성, side effect, 번들 출력 문제를 추적하기 쉬워집니다.
import type { User } from "./user";
export function getName(user: User) {
return user.name;
}한 줄에서 값과 타입을 함께 가져와야 한다면 named import 안에 type을 붙일 수 있습니다.
import { createUser, type User } from "./user";
const user: User = createUser();export도 같은 기준으로 나눕니다. 런타임 값은 export, 타입만 재노출하는 경우는 export type으로 표시합니다.
export { createUser } from "./user";
export type { User } from "./user";ESM과 CJS 경계
verbatimModuleSyntax는 모듈 시스템 혼동을 더 빨리 드러냅니다. 설정상 CommonJS로 emit되어야 하는 파일에 ESM import/export를 쓰면 TypeScript가 조용히 require로 바꾸는 대신 오류를 낼 수 있습니다. 이때는 module, moduleResolution, 파일 확장자, package.json의 type 필드를 함께 확인해야 합니다.
{
"type": "module"
}Node ESM 프로젝트라면 .mts, .cts, type: "module", module: "NodeNext" 같은 기준이 런타임과 맞아야 합니다. 프론트엔드 bundler 프로젝트라면 module: "ESNext" 또는 module: "Preserve" 계열과 bundler 설정의 역할 분담을 확인합니다.
여러 컴파일러를 거치는 프로젝트에서 특히 유용합니다. TypeScript는 타입 검사만 하고 Babel, swc, esbuild, bundler가 실제 변환을 맡는 구조라면 import가 타입 전용인지 값 의존성인지 소스 코드에 명시되어야 합니다.
{
"compilerOptions": {
"noEmit": true,
"verbatimModuleSyntax": true
}
}라이브러리 패키지처럼 JS 출력과 .d.ts 출력이 모두 중요하다면 더 신중하게 검토할 가치가 있습니다. 소비자 환경에서 side effect import가 빠지거나 타입 re-export가 값 export처럼 남는 문제를 줄일 수 있습니다.
주의할 점
verbatimModuleSyntax를 켜면 기존에 TypeScript가 자동으로 처리하던 import 정리가 더 엄격해집니다.
값 import와 타입 import를 의도대로 나누지 않으면 빌드 오류가 늘거나 런타임 import가 달라질 수 있습니다.
자동 정렬 도구나 lint 규칙도 함께 맞춰야 합니다. import type을 일반 import로 합치거나, side effect import를 정리해 버리는 규칙이 있으면 설정 의도가 흐려질 수 있습니다.
import "./polyfill";
import type { AppConfig } from "./config";참고 링크
2 sources