🔥 환경 한 번에: bun, tsx, tsc 6.0

849자
9분

bun, tsx, tsc 세 도구가 한 hello.ts 파일을 가운데 두고 각각 install/check/run 역할로 분담된 표지

처음 TypeScript 환경을 손으로 만들었을 때 나는 도구 이름 세 개가 한꺼번에 들어왔다. npm install -g typescripttsc 를 깔고, ts-node 도 따로 깔고, nodenpm 은 그대로 썼다. 같은 일을 두 가지 방식으로 처리하는 명령이 자꾸 함께 나와서 무엇을 골라야 할지 한참 고민했다. 시간이 좀 지나고 나서야 세 도구가 서로 다른 일을 맡는다는 점을 이해했다.

bun, tsc, tsx 의 역할은 뚜렷하다. bun 은 패키지 매니저와 런타임을 맡고, tsc 6.0 은 타입을 검사하고, tsx 는 빌드 없이 실행한다. 그래서 hello.ts 한 파일만 있으면 설치, 검사, 실행을 같은 디렉토리에서 차례로 확인할 수 있다.

세 도구가 각자 맡는 일

세 도구는 이름이 비슷해 보여도 역할이 다르다. 셋 다 TypeScript 와 관련이 있고, 셋 다 명령줄에서 한 줄로 부른다. 그래서 bun, tsx, tsc 가 같이 묶여 있는 걸 처음 봤을 때 나는 셋이 겹치는 것처럼 느꼈다.

실제로는 각자 다른 일을 맡는다.

  • bun 은 패키지 매니저와 런타임이다. bun install 로 의존성을 받고, bun run 으로 스크립트를 돌린다. node 와 npm 의 역할을 한 도구로 합친다.
  • tsc 는 TypeScript 6.0 의 타입 검사기다. tsc --noEmit 으로 코드의 타입 일관성만 검사하고 컴파일 산출물은 만들지 않는다.
  • tsx 는 TypeScript 직접 실행기다. tsx hello.ts 한 줄로 빌드 단계 없이 .ts 파일을 그대로 돌린다. 내부적으로 esbuild 가 타입 주석을 벗기고, 그 결과를 node 에 넘긴다.

셋 사이의 관계는 단순하다. bun 으로 환경을 깔고, tsc 로 타입을 검사하고, tsx 로 실제 코드를 돌린다. 한 .ts 파일을 두 명령에 차례로 넣는 구조다.

bun은 install과 run, tsc는 type check, tsx는 execute. 세 도구가 한 .ts 파일 위에서 서로 다른 단계를 맡는 다이어그램

한 디렉토리에 워크스페이스 만들기

hello.ts 한 파일을 돌리려면 디렉토리 하나에 package.json 한 개와 tsconfig.json 한 개가 있으면 된다. bun init 한 줄이 그 둘을 같이 만들어 준다.

bash
mkdir hello-ts && cd hello-ts
bun init -y
bun add -d typescript@6 tsx
bash
mkdir hello-ts && cd hello-ts
bun init -y
bun add -d typescript@6 tsx

결과는 package.json, tsconfig.json, bun.lock, 그리고 비어 있는 index.ts 다. tsconfig.json 의 기본값이 TS 6.0 의 권장 조합과 거의 같다. strict: true, target: "es2025", module: "nodenext", moduleResolution: "nodenext", verbatimModuleSyntax: true 가 한 줄씩 들어 있다. 다섯 옵션 모두 TS 6.0 권장 기본값 그대로다.

index.ts 를 지우고 hello.ts 를 새로 적는다.

ts
// file: hello.ts
export {};
 
const greet = (name: string): string => `Hello, ${name}!`;
 
console.log(greet("TypeScript"));
 
// 아래 줄의 주석을 풀면 tsc 가 멈춘다.
// console.log(greet(42));
// Argument of type 'number' is not assignable to parameter of type 'string'.
ts
// file: hello.ts
export {};
 
const greet = (name: string): string => `Hello, ${name}!`;
 
console.log(greet("TypeScript"));
 
// 아래 줄의 주석을 풀면 tsc 가 멈춘다.
// console.log(greet(42));
// Argument of type 'number' is not assignable to parameter of type 'string'.

// file: hello.ts 헤더는 본문 코드 블록의 약속이다. 모든 코드 블록 첫 줄에 파일 이름을 적어 둔다. export {}; 한 줄은 이 파일을 ESM 모듈로 만든다. 모듈로 두지 않으면 같은 함수 이름이 다른 파일에서 또 나올 때 컴파일러가 충돌로 본다.

이제 타입 검사를 한 줄로 돌린다.

bash
bunx tsc --noEmit
bash
bunx tsc --noEmit

출력이 한 줄도 없으면 통과다. --noEmit 옵션은 타입 검사만 하고 .js 파일을 만들지 않는다는 뜻이다. TS 6.0 에서 권장하는 모드다. 출력 .js 가 필요할 때는 별도 빌드 도구(esbuild, swc, bun build 등)에 넘기고, tsc 는 타입 검사 전담으로 둔다.

hello.ts 한 파일이 tsc --noEmit으로 들어가 타입 OK 표시가 콘솔에 나타나는 흐름 다이어그램

빌드 없이 그대로 돌리기

같은 hello.ts 를 그대로 실행한다.

bash
bunx tsx hello.ts
bash
bunx tsx hello.ts

출력은 Hello, TypeScript! 한 줄만 나온다. 별도 빌드 단계가 없다. tsx 는 esbuild 로 타입 주석만 벗기고 그 결과를 node 에 넘긴다. ts-node 를 써 본 적이 있다면 그 후속 도구라고 보면 된다. esbuild 기반이라 ts-node 보다 시작 속도가 빠르다.

tsc --noEmit 은 컴파일 시점에 타입을 검사하고 tsx hello.ts 는 런타임에 코드를 실행한다. 두 명령을 같은 .ts 파일에 차례로 적용하면 타입 안전성과 실행 결과를 함께 확인할 수 있다. 나는 새로운 .ts 파일을 만들 때마다 거의 반사적으로 두 명령을 차례로 친다.

hello.ts 한 파일이 tsx로 실행되어 콘솔에 Hello, TypeScript! 한 줄을 출력하는 흐름 다이어그램

타입 에러가 잡히는 방식

hello.ts 에서 주석으로 막아 둔 마지막 줄, console.log(greet(42)); 를 활성화한 상태를 가정한다. bunx tsc --noEmit 을 다시 돌리면 다음 출력이 나온다.

hello.ts:7:21 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

7 console.log(greet(42));
                    ~~

Found 1 error in hello.ts:7
hello.ts:7:21 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

7 console.log(greet(42));
                    ~~

Found 1 error in hello.ts:7

같은 상태에서 bunx tsx hello.ts 를 돌리면 실행은 그대로 된다. 출력은 Hello, TypeScript!Hello, 42! 두 줄이다. JavaScript 는 숫자 42 를 문자열로 자동 변환해서 결과를 만든다.

greet(42) 한 줄이 tsctsx 를 따로 두는 이유를 설명한다. 런타임은 결과를 끝까지 만들고, 타입 검사기는 그 결과가 의도와 어긋나는지 컴파일 시점에 막는다. 실무 코드 베이스에서는 tsc --noEmit 을 CI 의 첫 단계에 두고, 빨간 줄이 하나도 없는 상태에서만 실행을 허용한다.

tsc 는 TS2345 에러를 표시하고 tsx 는 Hello, 42! 두 줄을 출력하는 대비

다섯 단계로 묶이는 흐름

hello.ts 실습은 다섯 명령으로 끝난다. bun init 으로 디렉토리 하나에 package.jsontsconfig.json 을 만들고, bun add -d typescript@6 tsx 로 두 도구를 넣고, hello.ts 를 적고, bunx tsc --noEmit 으로 타입을 검사하고, bunx tsx hello.ts 로 실행한다. 환경 세팅은 여기까지다.

hello.ts 한 파일은 두 도구가 차례로 보는 시범 케이스다. tsc 는 컴파일 시점에 타입을 보고, tsx 는 런타임에 코드를 실행한다. 그래서 같은 함수가 값 레벨에서는 실행 대상이고 타입 레벨에서는 검사 대상이라는 점을 한 파일에서 바로 확인할 수 있다.

bun init부터 tsc 검사와 tsx 실행까지 다섯 단계가 한 줄로 이어진 환경 골격 다이어그램

YouTube 영상

채널 보기
투영과 예측, 그리고 선형 결합 | 선형대수학
AI를 위한 선형대수학 - 소개 | 선형대수학
트라이(Trie)를 이용한 자동 완성 알고리즘 | Trie 자료구조 이야기
트라이(Trie) 자료구조: 파이썬으로 삽입(Insert) 연산 구현하기 | Trie 자료구조 이야기
AI는 데이터를 어떻게 분류할까? 벡터의 거리와 KNN 알고리즘 | 선형대수학
행렬의 가장 중요한 연산 - 행렬 곱셈 | 선형대수학
내적의 기하학적 의미와 코사인 유사도 원리 | 선형대수학
직교성과 벡터 투영 | 선형대수학