Logo

타입스크립트 컴파일러 사용법 (tsc 커맨드)

이번 포스팅에서는 타입스크립트 코드를 자바스크립트로 코드로 컴파일하는 기본적인 방법에 대해서 알아보겠습니다.

타입스크립트 컴파일러

우선 타입스크립트 컴파일러(TypeScript compiler)가 무엇을 해주는 도구인지 간단히 짚고 넘어갈까요?

일반적으로 컴파일(compile)은 소스 코드를 특정 플렛폼에서 실행 가능한 형태로 변환하는 과정을 의미하는데요. 기존에는 C++나 Java와 같이 자체 타입 시스템을 가진 프로그래밍 언어에서 주로 다뤄지던 개념이었으나, 타입스크립트의 등장 이후로는 자바스크립트에서도 컴파일이라는 용어를 심심치 않게 들을 수 있게 되었습니다.

브라우저에서 실행되던 자바스크립트는 전통적으로 컴파일이 필요없는 대표적인 인터프리터(interpreter) 언어였는데요. 브라우저나 Node.js는 우리가 작성한 자바스크립트 코드를 있는 그대로 이해하고 바로 실행할 수 있기 때문입니다.

하지만 자바스크립트 대신에 타입스크립트로 코드를 작성하는 것이 보편화됨에 따라 이제 많은 자바스크립트 프로젝트에서 컴파일 과정이 필요하게 되었습니다. 브라우저는 적어도 아직까지는 타입스크립트 코드를 있는 처리할 수가 없기 때문인데요. 따라서 개발 단계에서는 타입스크립트로 코드를 작성하지만, 배포할 때는 반드시 코드를 자바스크립트로 변환해줘야 하죠.

아이러니하게도 타입스크립트로 개발을 함으로써 좀 더 견고한 프로그램을 짤 수 있게 되었지만, 그에 대한 댓가로 기존에 없었던 컴파일이라는 추가적인 과정이 필요하게 되버린 것이죠. 😅

이렇게 타입스크립트 코드를 자바스크립트로 코드로 변환할 때 필요한 도구가 바로 타입스크립트 컴파일러인데요. 타입스크립트 컴파일러는 코드 변환 과정에서 소위, 정적 타입 검사(static type check)를 해주기 때문에 프로그램 버그를 예방하는데도 활용됩니다.

타입스크립트 패키지 설치

타입스크립트 컴파일러는 자바스크립트의 패키지 매니저인 npm을 이용하면 터미널 상에서 손쉽게 설치할 수 있습니다.

프로젝트 별로 다른 버전의 타입스크립트를 사용해야하는 경우 대비하여 일반적으로 프로젝트 범위에서 타입스크립트 패키지를 설치해주는 것이 권장되는데요. 앞에서 설명드린 것처럼 타입스크립트 컴파일러는 컴파일 시점에만 필요하고 프로그램의 실행 시점에는 필요하지 않기 때문에 개발 의존성(dev dependency)으로 설치합니다.

$ npm i -D typescript

이렇게 프로젝트에 설치된 타입스크립트 컴파일러를 실행하려면 앞에 npx를 붙이고 tsc 커맨드를 실행하면 됩니다. 설치가 잘 되었는지 확인하기 위해서 -v 옵션으로 컴파일러의 버전을 출력해보겠습니다.

$ npx tsc -v
Version 4.7.4

만약에 타입스크립트 컴파일러를 프로젝트의 구분없이 전역에서 사용하고 싶다면 -g 옵션을 사용해서 설치해주면 됩니다.

$ npm i -g typescript

이렇게 전역에 타입스크립트 컴파일러를 전역에 설치하면 컴퓨터의 어떤 경로에서나 tsc 커맨드를 실행할 수 있게 됩니다.

$ tsc -v
Version 4.7.4

타입스크립트 코드 작성

그럼 이제부터 실습용으로 hello.ts라는 파일에 간단한 타입스크립트 코드를 작성해볼까요?

hello.ts
let message = "안녕하세요!";
console.log(message);

이거 자바스크립트 코드 아니냐고요?

네, 맞습니다.사실 이 코드는 자바스크립트 코드이지만 동시에 타입스크립트 코드도 될 수 있습니다. 타입스크립트는 자바스크립트의 상위 집합(super set), 즉 타입스크립트의 문법은 모든 자바스크립트의 문법을 포함하니까요. 여기서는 단지 확장자가 .ts인 파일에 저장되어 있는 코드이기 때문에 엄밀히 말해서 타입스크립트 코드라고 보는 게 맞겠습니다.

tsc 커맨드의 인자로 타입스크립트 파일명을 넘기면 해당 파일에 저장되어 있는 소스 코드가 자바스크립트로 변환됩니다.

$ npx tsc hello.ts

컴파일의 결과물로 동일한 디렉토리에 파일 이름은 같지만 확장자가 .js인 파일이 떨궈질 것입니다.

hello.js
var message = "안녕하세요!";
console.log(message);

이것이 컴파일의 결과물인 자바스크립트 파일인데요. hello.js 파일을 열어보시면 컴파일된 코드는 타입스크립트 파일 내의 소스 코드와 대동소이한 것을 볼 수 있습니다.

타입 어노테이션 추가

타입스크립트에서는 타입 어노테이션(annotation)을 사용하여 코드에 타입을 명시해줍니다.

예를 들어, message라는 변수에는 문자열 타입의 값이 저장되야 한다고 명시해줘보겠습니다.

hello.ts
let message: string = "안녕하세요!";
console.log(message);

이 타입스크립트 파일을 다시 컴파일해볼까요?

$ npx tsc hello.ts

컴파일된 자바스크립트 파일을 열어 보시면 타입 어노테이션이 사리진 것을 알 수 있습니다.

hello.js
var message = "안녕하세요!";
console.log(message);

위에서 설명드린 컴파일러의 목적을 상기해보시면 이는 당연한 결과일 것입니다. 브라우저나 Node.js 환경에서는 타입 어노테이션은 문법 오류로 인식이 되기 때문에 컴파일 과정에서 제거가 되는 것이지요.

정적 타입 검사

타입스크립트의 컴파일러는 단순히 타입스트립트 코드를 자바스크립트로 변환해줄 뿐만 아니라 정적 타입 검사를 통해서 코드가 실행될 때 발생할 수 있는 문제를 사전에 알려줍니다.

예를 들어, 위에서 작성한 코드에서 메시지를 출력해주는 부분을 별도의 함수 hello()로 빼내보겠습니다.

hello.ts
const hello = (message) => {
  console.log(message);
};

let message: string = "안녕하세요!";
hello(message);

수정된 소스 코드를 동일한 방법으로 컴파일하면 아무 문제없이 자바스크립트 코드로 컴파일될텐데요.

hello.js
var hello = function (message) {
  console.log(message);
};
var message = "안녕하세요!";
hello(message);

사실 hello() 함수에는 매개 변수에 타입이 명시되어 있지 않아서 문제의 소지가 있는데 타입스크립트 컴파일러는 왜 모른척할까요? 이것은 타입스크립트 컴파일러가 기본적으로는 상당히 느슨한 기준에 따라 타입 검사를 하기 때문입니다.

엄격한 기준에 따라 타입 검사를 하고 싶다면 tsc 커맨드를 실행할 때 strict 옵션을 주면되는데요. 이제야 우리가 기대했던 타입 오류가 발생하고 있습니다.

$ npx tsc --strict hello.ts
hello.ts:1:16 - error TS7006: Parameter 'message' implicitly has an 'any' type.

1 const hello = (message) => {
                 ~~~~~~~


Found 1 error in hello.ts:1

이 타입 오류를 해결하기 위해서 hello() 함수의 매개 변수의 타입을 명시해주었습니다.

hello.ts
const hello = (message: string) => {
  console.log(message);
};

let message: string = "안녕하세요!";
console.log(message);

다시 동일하게 strict 옵션을 줘서 컴파일을 해보면 이 번에는 타입 오류없이 컴파일됩니다.

hello.js
"use strict";
var hello = function (message) {
  console.log(message);
};
var message = "안녕하세요!";
console.log(message);

자바스크립트 타켓 지정

눈썰미가 좋으신 분들은 컴파일된 코드에서 letvar로 바뀌고 =>function으로 바뀌는 현상을 눈치채셨을 텐데요. 이것은 타입스크립트 컴파일러가 자동으로 오래된 문법의 자바스크립트 코드로 변환해주기 때문입니다. 인터넷 익스플로러와 같이 구식 브라우저에서도 컴파일된 코드가 잘 돌아가게 해주기 위함이죠.

Babel과 같은 트랜스파일러(transfiler)가 최신 문법의 자바스크립트로 작성된 코드를 브라우저 하위 호완성 보장을 위해서 예전 문법으로 변환해주는 것과 비슷한 이치라고 볼 수 있겠습니다.

만약에 이렇게 구식 브라우저를 지원할 필요가 없다면 tsc 커맨드의 target 옵션을 줘서 좀 더 최근 문법의 자바스크립트로 변환해줄 수 있는데요.

예를 들어, 동일한 소스 코드를 ES6(ES2015) 문법으로 컴파일을 해보겠습니다.

$ npx tsc --target es6 hello.ts

이번에는 컴파일된 자바스크립트 코드에서 작성했던 원래 문법이 그대로 유지되는 것을 볼 수 있네요.

hello.js
const hello = (message) => {
    console.log(message);
};
let message = "안녕하세요!";
console.log(message);

더 최근 문법으로 컴파일하고 싶다면 es2016, es2017, es2018 이렇게 연도별로 올릴 수 있고, target 옵션을 esnext으로 주면 가장 최신의 문법을 사용하게 됩니다. 아무래도 불특정 다수를 상대로하는 B2C 서비스에서는 컴파일할 때 자바스크립트 타겟을 어느 정도 보수적으로 설정하는 것이 유리하겠죠?

자바스크립트 모듈 지정

현재 자바스크립트 생태계에서는 안타깝게도 여러 종류의 모듈 시스템이 공존하고 있지요? 물론 ES 모듈 시스템으로 통일하려는 움직임이 있지만 Node.js 환경에서 CommonJS를 오랫동안 사용해왔기 때문에 전환하는데 시간이 좀 걸리지 않을까 싶습니다.

다행히도 타입스크립트로 코드를 작성할 때는 ES 모듈 시스템의 importexport 키워드를 사용해서 모듈을 불러오고 내보내기를 하는데요. 문제는 컴파일의 결과물인 자바스크립트 코드가 어떤 모듈 시스템의 문법을 사용해느냐죠.

예를 들어, utils.ts라는 별도의 파일을 생성 후에 hello() 함수를 그 파일로 옮긴 후에 내보내겠습니다.

utils.ts
export const hello = (message: string) => {
  console.log(message);
};

그럼 기존 hello.ts 파일에서는 utils.ts 파일에서 내보내는 hello() 함수를 불러와서 사용할 수 있겠죠?

hello.ts
import { hello } from "./utils";

let message: string = "안녕하세요!";
hello(message);

이제 이 두 개의 타입스크립트 파일을 컴파일하면 두 개의 자바스크립트 파일이 생길텐데요.

$ npx tsc hello.ts utils.ts

컴파일된 코드를 자세히 보시면, CommonJS에서 쓰던 키워드인 exportsrequire를 사용되는 것을 볼 수 있습니다.

utils.js
"use strict";
exports.__esModule = true;
exports.hello = void 0;
var hello = function (message) {
  console.log(message);
};
exports.hello = hello;
hello.js
"use strict";
exports.__esModule = true;
var utils_1 = require("./utils");
var message = "안녕하세요!";
(0, utils_1.hello)(message);

컴파일된 코드를 Node.js의 구식 버전과 같이 CommonJS를 모듈 시스템으로 사용하는 실행 환경에서 돌릴 게 아니라면 표준으로 자라잡고 있는 ES 모듈의 문법을 사용해서 컴파일해야할 것입니다.

tsc 커맨드의 module 옵션을 사용하면 컴파일할 때 어떤 모듈 시스템의 문법을 사용할 건지를 설정해줄 수 있습니다.

예를 들어, ES 모듈의 문법을 사용하기 위해 module 옵션을 es6로 주고 다시 컴파일을 해보겠습니다.

$ npx tsc --module es6 hello.ts utils.ts

다시 컴파일된 자바스크립트 파일을 확인해보면 exportimport 문법이 사용된 것이 확인됩니다.

utils.js
export var hello = function (message) {
  console.log(message);
};
hello.js
import { hello } from "./utils";
var message = "안녕하세요!";
hello(message);

tsconfig.json

설정이 필요한 옵션이 많아지게 되면 입력해야하는 tsc 커맨드가 길어지게 되어 힘들어지는데요. tsconfig.json 파일에 설정 내용을 저장해놓으면 굳이 tsc 커맨드를 실행할 때 마다 옵션을 일일이 줄 필요가 없어집니다.

지금까지 다룬 3개의 옵션이 모두 적용되도록 tsconfig.json 파일을 작성해볼까요?

{
  "compilerOptions": {
    "strict": true,
    "target": "es6",
    "module": "es6"
  }
}

이제 모든 옵션을 생략하고 tsc 커맨드만 실행해보면 tsconfig.json 파일에 명시된 모든 옵션이 적용되어 컴파일이 됩니다.

$ npx tsc

컴파일된 자바스크립트 코드에는 ES6 문법과 ES 모듈이 사용된 것을 확인할 수 있습니다. 🎉

utils.js
export const hello = (message) => {
  console.log(message);
};
hello.js
import { hello } from "./utils";
let message = "안녕하세요!";
hello(message);

마치면서

이상으로 타입스크립트 코드를 어떻게 자바스크립트로 컴파일할 수 있는지에 대해서 살펴보았습니다. 사실 실제 프로젝트에서 개발자가 터미널에서 직접 tsc 커맨드를 날라가면서 코딩을 하는 일은 별로 없을 것입니다. 대부분의 경우, 타입스크립트 컴파일러는 이미 프로젝트에 셋업된 자동화된 개발 도구나 CI를 통해 알게 모르게 수시로 실행되고 있을테니까요.

하지만 개발자로서 타입스크립트 컴파일러가 기본적으로 어떻게 동작하는지 이해하고 있는 것과 아닌 것은 큰 차이가 있을 것입니다. 당장 개발 도구나 상용 서버에서 컴파일 관련 문제가 발생했을 때 스스로 원인을 찾고 해결하는데 큰 도움이 될 거라고 생각합니다. 뿐만 아니라 경력이 늘어나면서 이러한 편리한 개발 환경을 이용하는 입장에서 구축하는 입장이 될 수도 있을테니까요.