[TS] TypeScript in Node.js

장동균·2025년 5월 6일
0

State of JavaScript 2024: Usage

2024 State of JS에 의하면
JavaScript보다 TypeScript를 더 많이 사용하는 비율은 67%에 달한다.

TypeScript를 조금이라도 사용하고 있다고 답한 비율은 92%에 달하면서 이제는 TypeScript의 사용이 필수적이라는 사실을 알 수 있다.

압도적인 사용량에도 불구하고 Node.js는 v22.6.0 이전 버전까지 TypeScript 파일을 실행하기 위한 기능을 제공하지 않았다. TypeScript 파일을 Node.js 환경에서 실행하기 위해서는 ts-node, tsc와 같은 별도의 도구가 필요했다.

Node.js 환경 위에 추가적인 도구를 설치하여 TypeScript 파일을 다루는 것은 불편할 뿐만 아니라 성능적인 측면에서도 좋지 않다.

Node.js는 이러한 문제를 해결하기 위해 몇가지 옵션을 추가한다.


--experimental-strip-types

node --experimental-strip-types example.ts
Added in: v22.6.0

Command-line API | Node.js v22.15.0 Documentation

타입 선언부를 모두 제거하여 별도의 트랜스파일 없이 TypeScript 파일을 실행한다.
타입만을 모두 제거하고, 타입 검사나 코드 변환은 전혀 수행하지 않는다.
타입 검사나 전통적인 트랜스파일 과정을 생략하면, 타입스크립트에 영향을 받지 않고, 별도의 설정 없이도 코드를 실행할 수 있게 된다.

const foo: string = 'foo';

const foo         = 'foo';
const func = (a: string) => {}

const func = (a        ) => {}

타입 선언부만을 제거하는 처리는 분명한 한계를 가진다.

  • enum declarations
  • namespaces and modules with runtime code
  • parameter properties in classes
  • import aliase

(위의 TypeScript-only Syntax들을 unErasableSyntax라고도 부른다.)

TypeScript는 JavaScript에 존재하지 않는 몇가지 문법들을 사용하고 있으며, 해당 구문이 단순히 제거된다면 프로그램 전체에 큰 영향을 미친다.

enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 1] = "Up";
    Direction[Direction["Down"] = 2] = "Down";
    Direction[Direction["Left"] = 3] = "Left";
    Direction[Direction["Right"] = 4] = "Right";
})(Direction || (Direction = {}));

TypeScript의 기능인 enum은 자바스크립트로 트랜스파일되어야 실행될 수 있다. 단순히 strip 처리되면 문제가 생긴다.


--experimental-transform-types

node --experimental-transform-types another-example.ts
Added in: 22.7.0

TypeScript-only Syntax를 사용했을 때 발생할 수 있는 문제를 해결하기 위한 옵션이다.
이를 활성화 하면 트랜스파일을 위한 소스맵이 생성되고 이를 통해 기존 ts 문법을 사용할 수 있게 된다.

해당 옵션을 활성화 하면 자동으로 strip 옵션은 활성화되기 때문에 둘을 함께 설정할 필요는 없다.


v23.6.0 버전부터는
--experimental-strip-types 옵션이 Node.js의 기본 기능이 되었다.
해당 기능을 사용하지 않는다면 --no-experimental-strip-types 설정을 통해 비활성화가 가능하다.


이러한 흐름에 맞춰 typescript에서의 대응 또한 추가되었다.

--experimental-strip-types 옵션의 default 설정으로, 별도의 처리 없이 TypeScript-only Syntax를 사용하면 문제가 생길 수 있다.

Announcing TypeScript 5.8 Beta - TypeScript
이 문제를 해결하기 위해 TypeScript에서도 별도의 옵션을 추가했다.
TypeScript v5.8에 추가된 --erasableSyntaxOnly 옵션을 활성화하면, TypeScript-only Syntax를 사용할 때 타입 에러가 발생한다.


앞으로

Future versions of Node.js will include support for TypeScript without the need for a command line flag.

Node.js의 설명을 보면 --experimental-transform-types 옵션 또한 기본 옵션이 될 가능성이 높다.

--experimental-strip-types는 별도의 소스맵을 만들지 않지만 --experimental-transform-types는 별도의 소스맵을 만들어낸다.

타입과 관련된 코드를 단순히 blank 처리하는 strip-types와는 달리 transform은 트랜스파일을 수행한다. 일반적인 트랜스파일 과정에서는 소스맵(source maps) 을 생성하고 분석하여 원본 코드의 위치를 역추적한다. 이는 오류 추적과 디버깅에 필수적이다. 개발자가 작성한 코드와 실제 실행되는 코드 간의 차이 때문에, 소스맵 없이는 문제를 찾기 어렵다.

// test.ts
function crash() {
  throw new Error("Boom");
}

crash();
// 소스맵이 없으면
Error: Boom
    at crash (/path/test.js:2:9)

// 소스맵이 있으면
Error: Boom
    at crash (/path/test.ts:2:9)

하지만 이 과정은 비용이 든다 — 소스맵을 생성하고, 불러오고, 스택 트레이스를 재구성하는 데 자원이 소모된다.

효율적인 처리를 위해서는, strip-types 설정만으로 충분하도록 TypeScript-only Syntax 사용을 지양하는 것이 좋겠다.


참고문헌

Vercel Security Checkpoint
Announcing TypeScript 5.8 Beta - TypeScript
TypeScript 5.8 Ships --erasableSyntaxOnly To Disable Enums | Total TypeScript
Node.js — Running TypeScript Natively
Modules: TypeScript | Node.js v23.11.0 Documentation
State of JavaScript 2024: Usage


Node.js가 타입 검사를 수행할 수 없는 이유 (번외 - 참고한 블로그 내용에 대한 번역)

수년간 TypeScript를 지원하기 위한 다양한 방법들이 모색되었지만, 그 모든 방법은 플래그나 설정 파일을 통한 추가적인 구성(configuration) 을 필요로 했고, 이는 개발자 경험을 복잡하게 만들었습니다.

가장 그럴듯해 보이는 해결책 중 하나는 tsc(TypeScript 컴파일러)를 Node.js에 직접 내장하는 것이었지만, 이는 상당한 어려움을 수반합니다.

첫 번째로, Node.js는 안정성(stability) 을 최우선으로 합니다. Node.js는 세 가지 숫자의 버전 체계(semver) 와 거의 3년에 이르는 장기 지원(LTS) 주기를 제공하기 때문에, tsc의 빈번한 업데이트로 인한 타입 검사 로직의 변경은 Node.js에 치명적일 수 있습니다. tsc의 사소한 변경조차 타입 체크의 동작에 영향을 줄 수 있으며, 이로 인해 실제 프로덕션 배포에서 오류가 발생할 수 있습니다.

두 번째, TypeScript의 타입 검사는 tsconfig.json 파일에 강하게 의존합니다. 이 설정 파일은 사용 중인 tsc 버전과 지원되는 문법(syntax)에 정확히 맞아야 합니다. 만약 Node.js에 기본적인 tsconfig 설정을 내장한다 해도, 대부분의 사용자 프로젝트와는 맞지 않을 가능성이 높기 때문에 그다지 의미가 없습니다.

세 번째, tsc 패키지의 크기는 약 22MB로, Node.js에 내장하기에는 너무 큽니다. 이 정도 용량의 의존성을 포함하면 Node.js 런타임의 바이너리 크기가 상당히 커지게 되며, 이는 특히 AWS Lambda와 같은 서버리스 환경이나 메모리가 제한된 플랫폼에서는 심각한 문제가 됩니다.

일상적인 개발에서는 IDE에서 제공하는 TypeScript Language Server가 이미 실시간 타입 피드백을 제공하고 있습니다. 이는 개발 중 계속 실행(check)하지 않아도 타입 오류를 바로 볼 수 있게 해주므로, 런타임에 타입 체크를 수행할 필요성을 줄여줍니다.

그래서 대부분의 경우 타입 검사는 CI 파이프라인이나 커밋 훅(git hooks)에서 수행되며, 빠른 개발 흐름을 방해하지 않습니다. 이런 이유로 tsc는 런타임에 포함시키기보다는 devDependency로 설치하는 것이 훨씬 합리적입니다. 이렇게 하면 자신이 원하는 tsc 버전과 tsconfig 설정을 자유롭게 사용할 수 있고, 런타임에 강제로 맞추지 않아도 됩니다.

profile
프론트 개발자가 되고 싶어요

1개의 댓글

comment-user-thumbnail
2025년 6월 17일

동균스크립트

답글 달기