타입스크립트 enum의 종말, Node.js 호환성을 위한 as const 활용법

November 9, 20254 minutes

노드제이에스(Node.js) v22.6.0 버전
노드제이에스(Node.js) v22.6.0 버전

노드제이에스(Node.js) v22.6.0 버전에 타입스크립트 파일을 직접 실행할 수 있게 해주는 ‘타입 제거(type-stripping)’ 지원 기능이 도입되었는데요.

이 기능 때문에 우리가 오랫동안 사용해 온 전통적인 ’enum’이 더 이상 제대로 동작하지 않게 되었습니다.

그래서 오늘은 왜 ‘as const’ 객체가 노드의 새로운 기능과 완벽하게 호환되는 현대적이고 미래 지향적인 대안인지 자세히 알려드리겠습니다.

노드제이에스(Node.js)가 v22.6.0에서 타입 제거 기능을 선보이면서 타입스크립트를 작성하는 방식에 근본적인 변화가 생겼거든요.

특히 v22.18.0부터는 이 기능이 기본으로 활성화되어, 별다른 플래그 없이 node file.ts 명령어만으로 .ts 파일을 바로 실행할 수 있게 되었습니다.

물론 이전 버전(v22.6.0 ~ v22.17.0)에서는 --experimental-strip-types 플래그를 명시적으로 사용해야 했는데요.

하지만 이 방식은 코드가 ‘제거 가능한(erasable)’ 타입스크립트 문법만을 사용했을 때만 유효합니다.

즉, 노드가 별도의 빌드 과정 없이 런타임에 타입 정의만 쏙 제거하고 코드를 실행하는 것입니다.

대부분의 타입스크립트 기능은 이 방식 덕분에 정말 마법처럼 편리해졌는데요.

하지만 안타깝게도 이 우아한 흐름을 깨뜨리는 한 가지 예외가 있는데, 바로 전통적인 ’enum’입니다.

타입 제거의 혁신

노드의 타입 제거 기능은 표준 타입스크립트 문법과 아주 매끄럽게 연동되는데요.

앞서 말했듯 v22.18.0 버전부터는 기본으로 활성화됩니다.

이제 아래와 같은 코드를 작성하고 node example.ts 명령어로 바로 실행할 수 있습니다.

function greet(name: string) {
  console.log(`Hello, ${name}!`);
}

greet('TypeScript');

노드는 그저 타입 정의 부분(: string)을 제거하고 그 아래에 있는 자바스크립트 코드만 실행하는데요.

더 이상 타입스크립트 컴파일러도, 복잡한 빌드 설정도, 컴파일을 기다리는 시간도 필요 없게 된 것입니다.

개발자들이 오랫동안 기다려온, ‘컴파일 없는 타입스크립트’ 경험이 드디어 현실이 된 셈입니다.

Enum이 일으키는 문제

하지만 기존의 타입스크립트 enum은 이런 흐름에 문제를 일으키는 주범이거든요.

왜냐하면 enum은 단순히 지워질 수 있는 타입 문법이 아니기 때문입니다.

enum을 정의하면, 타입스크립트는 실제로 런타임 코드를 생성해서 객체를 만들어내는데요.

아래 예시를 한번 살펴보겠습니다.

enum Color {
  Red,
  Green,
  Blue,
}

console.log(Color.Red);

이 코드를 노드에서 직접 실행하려고 하면 다음과 같은 상황이 발생합니다.

node color.ts

그러면 곧바로 이런 에러를 마주하게 될 것입니다.

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode

이런 문제가 발생하는 이유는 enum이 단순한 타입 제거가 아니라 ‘코드 변환’을 필요로 하기 때문인데요.

노드의 내장 타입 제거기는 제거 가능한 문법만 처리할 수 있을 뿐, 런타임 객체를 생성해야 하는 코드는 변환하지 못합니다.

물론 v22.7.0부터 사용 가능한 --experimental-transform-types 플래그를 사용하면 이 문제를 해결할 수는 있거든요.

이 플래그는 타입 제거 기능을 자동으로 활성화하고 enum과 네임스페이스 지원을 추가해 줍니다.

하지만 실제 프로덕션 코드에 ‘실험적인(experimental)’ 플래그에 의존하는 것은 결코 좋은 생각이 아닙니다.

해당 플래그의 동작 방식은 향후 버전에 따라 얼마든지 변경될 수 있으며, 제거 가능한 문법만 사용하는 것이 최대의 호환성을 보장하는 길입니다.

현대적인 대안 as const

노드의 타입 제거 아키텍처와 씨름하는 대신, ‘as const’를 활용한 일반 객체로 동일한 기능을 구현할 수 있는데요.

이 패턴을 사용하면 노드의 ‘제거 가능한’ 타입 전용 접근 방식과 완벽하게 호환되면서도 명확성과 타입 안정성을 모두 확보할 수 있습니다.

const Color = {
  RED: 'red',
  GREEN: 'green',
  BLUE: 'blue',
} as const;

type Color = (typeof Color)[keyof typeof Color];

function paint(color: Color) {
  console.log(`Painting with "${color}"`);
}

paint(Color.RED);

이 접근 방식은 여러 장점을 제공하는데요.

이는 순수한 자바스크립트와 타입의 조합이기 때문에 노드의 타입 제거 기능과 완벽하게 작동합니다.

런타임 오버헤드가 전혀 없고, 특별한 컴파일러 변환도 필요 없거든요.

런타임 상수와 컴파일 타임의 타입 안정성을 모두 지킬 수 있는 아주 효율적인 방법입니다.

또한 이 패턴은 이에스린트(ESLint), 번들러, 타입 체커와 같은 최신 개발 도구와도 아주 잘 어울립니다.

타입 유니언은 언제 사용할까

만약 런타임 객체가 전혀 필요 없고, 단순히 허용되는 문자열 값의 집합만 정의하고 싶을 때도 있는데요.

그럴 때는 ‘타입 유니언(type unions)‘을 사용하는 훨씬 간단한 방법이 있습니다.

type Role = 'admin' | 'editor' | 'viewer';

이 문법은 완전히 제거 가능하기 때문에 노드에 의해 아무런 런타임 흔적 없이 깔끔하게 제거되는데요.

런타임 상수는 필요 없고 오직 컴파일 시점의 타입 체크만 필요한 상황에 가장 이상적인 해결책입니다.

컴파일 시간에 오류 잡아내기

타입스크립트 5.8 버전에는 런타임에 발생할 호환성 문제를 미리 방지할 수 있는 유용한 기능이 추가되었거든요.

바로 ‘–erasableSyntaxOnly’라는 컴파일러 플래그입니다.

이 플래그를 활성화하면, 타입스크립트는 파일에서 제거될 수 있는 구문만 허용하고 변환이 필요한 구문을 만나면 컴파일 시점에 바로 오류를 알려줍니다.

즉, enum, 런타임 코드가 포함된 네임스페이스, 클래스의 매개변수 속성, import 별칭 등을 사용하려고 할 때 편집기에서 즉각적인 피드백을 받을 수 있다는 건데요.

노드에서 코드를 실행해 보고 나서야 비호환성을 발견하는 대신, 개발 중에 타입스크립트가 미리 경고해 주기 때문에 노드의 타입 제거 기능과 완벽하게 호환되는 코드를 훨씬 쉽게 작성할 수 있습니다.