2023년 3월 16일 목요일

VS Code의 TypeScript 디버깅

 

VS Code의 TypeScript 디버깅

Visual Studio Code(VS Code)는 TypeScript를 디버깅하는 기능을 기본적으로 가지고 있다. TypeScript 소스는 JavaScript로 컴파일되어 실행되는데, 원래의 TypeScript 소스와 실행되는 JavaScript 사이의 매핑 정보를 가지고 있는 파일을 소스 맵(source map)이라 한다. Visual Studio Code는 TypeScript 디버깅시 이 source map을 사용하여 실행되는 JavaScript에 상응하는 TypeScript 소스 정보를 얻게 된다.

TypeScript 소스코드에 대한 source map 파일(*.map)을 생성하기 위해서는, sourceMap 컴파일러 옵션을 true로 설정하면 된다. 예를 들어, 아래는 test.ts 로부터 test.js 파일과 source map인 test.js.map 파일을 생성하는 명령이다.

    C> tsc test.ts --sourceMap true

Visual Studio Code에서 디버깅을 위해, 흔히 Run 메뉴의 Start Debugging 메뉴 (F5)를 사용하는데, 이를 실행하면 TypeScript 프로젝트 구성파일인 tsconfig.json 을 사용하여 빌드하고 디버깅하게 된다. 따라서, 프로젝트 루트 폴더에 tsconfig.json 파일을 생성하고, 아래와 같이 tsconfig.json 파일의 compilerOptions 밑에 "sourceMap": true 를 추가하여 source map 파일을 생성하도록 설정한다. Visual Studio Code는 tsconfig.json 파일을 편집할 때, 인텔리센스 기능을 제공하므로 편리하다. 만약 sourceMap 옵션을 지정하지 않으면, source map 파일이 생성되지 않으며, 따라서 디버깅할 수 없다. 참고로 아래 outDir 옵션은 JavaScript 파일과 source map 파일이 출력되는 폴더 위치이다.

    {
        "compilerOptions": {
            "sourceMap": true,
            "outDir": "out"
        }
    }

Visual Studio Code에서 TypeScript 소스코드를 열고 필요한 곳에 중단점(breakpoint)을 두고, F5 (혹은 Run/Start Debugging)를 누르면, 프로젝트가 빌드되고 디버깅이 시작된다. 아래는 중단점을 두고 디버깅을 시작했을 때의 화면이다. 좌측에 사용되는 변수들의 값들을 볼 수 있고, 우측 상단에 디버거 컨트롤들을 볼 수 있다.

위의 경우는 NodeJS를 사용하여 JavaScript를 실행한 것이다. 만약 Browser 상에서 HTML 안의 JavaScript를 디버깅할 경우에는 VS Code에 "Debugger for Chrome" 혹은 "Debugger for FireFox" Extension을 설치하고 이 Debugger를 사용할 수 있다. VS Code에 "Debugger for Chrome" 등의 디버거가 설치된 경우, F5를 누르면 디버거로 Chrome을 사용할 지, Node를 사용할 지 선택하게 한다. 이때 Chrome을 선택하면 launch.json 구성파일(.vscode/launch.json)을 보여주는데, 이때 로컬 웹서버 url을 제대로 설정하면 디버깅을 시작할 수 있다.

TypeScript 프로젝트

 

TypeScript 프로젝트

TypeScript 프로젝트는 (임의의) 루트 폴더와 그 서브디렉토리에 있는 모든 .ts 파일들을 포함하는 것으로, 루프 폴더에 tsconfig.json 이라는 config 파일을 갖는다. TypeScript 프로젝트는 TypeScript 파일(*.ts)을 개별적으로 컴파일하는 대신, 프로젝트 안에 있는 모든 TypeScript 파일들을 한꺼번에 컴파일할 수 있게 한다.

특정 디렉토리에 있는 모든 TypeScript 파일들을 관리하기 위해 TypeScript 프로젝트를 만들어 사용하는데, 현재 작업하는 디렉토리를 TypeScript 프로젝트로 만들기 위해서는 tsconfig.json 파일을 수작업으로 생성하거나 혹은 보다 손쉽게 tsc --init 명령을 실행하면 된다.

    # tsconfig.json 파일 생성
    C:\myapp> tsc --init

tsconfig.json 파일에는 TypeScript 컴파일러 옵션들이 들어가 있는데, 만약 비어 있으면 TypeScript 컴파일러의 디폴트 옵션들을 사용한다. tsconfig.json을 루트 폴더에 넣고, 프로젝트 전체를 컴파일하기 위해서는 별도의 옵션없이 "tsc" 명령을 사용한다.

    # TypeScript 프로젝트 전체 컴파일
    C:\myapp> tsc

예를 들어, 아래와 같이 2개의 TypeScript 파일(ex1.ts, ex2.ts)이 임의의 프로젝트 폴더에 있고, 해당 프로젝트 루트 폴더 안에 tsconfig.json 파일이 있다고 가정하자. tsconfig.json 에는 여러 컴파일러 옵션을 지정할 수 있지만, 현재는 비어 있다고 가정한다. (즉, tsconfig.json은 JSON 파일이므로 "{ }" 이 있다고 가정) 이 경우 아래와 같이 tsc 를 실행하면 해당 폴더의 *.ts 파일들이 컴파일되어 *.js 파일들(ex1.js, ex2.js)이 생성된다.

만약 tsconfig.json 파일이 루트폴더에 있지 않고 다른 폴더에 위치한다면, "tsc --project" 옵션을 사용하여 해당 경로를 지정할 수 있다.

    C:\myapp> tsc --project c:\src\tsconfig.json

한가지 주의할 점은 tsconfig.json 가 이처럼 다른 폴더(C:\src)에 있을 때, C:\src 안에 적어도 하나의 .ts 파일이 있어야 한다는 것이다. 만약 없으면 빈 .ts 파일을 하나 만들어 놓아야 한다. 그렇지 않으면, error TS18003: No inputs were found in config file 'c:/src/tsconfig.json' 와 같은 에러를 발생시킬 수 있다.

tsconfig.json 옵션들

TypeScript 프로젝트를 컴파일할 때 사용하는 tsconfig.json 파일에는 tsc 컴파일러 옵션들을 지정할 수 있다. tsc 컴파일러 옵션은 -- 혹은 - 으로 표시되는데, -- 옵션은 긴 명령옵션을 쓸 때 사용하고, - 옵션은 -- 옵션의 단축형으로 짧게 쓰기 위해 사용된다. 예를 들어, tsc --module 옵션은 긴 명령옵션을 사용한 것이고, 이를 줄여 tsc -m 과 같이 사용할 수 있다. 이러한 tsc 명령옵션들을 tsconfig.json 파일 안의 "compilerOptions"에서 그대로 사용할 수 있다. 아래는 3개의 TypeScript 컴파일러 옵션을 사용한 tsconfig.json 파일의 예이다.

    {
        "compilerOptions": {
            "target": "ES6",
            "removeComments": true,
            "pretty": true
        }
    }

tsconfig.json 파일에서 특정 *.ts 파일들만 컴파일하도록 지정할 수 있는데, 이는 아래와 같이 files 값을 지정하면 된다.

   {
       "compilerOptions": {
           "target": "ES6",
        },
        "files": [
           "ex1.ts",
           "ex2.ts",
           "ex3.ts"
        ]
   }

tsconfig.json 파일에 또 다른 옵션으로 include, exclude 옵션이 있는데, include는 컴파일에 포함하는 파일들을 나타내고, exclude는 include 대상 중 제외시켜야 하는 파일들을 나타낸다. 아래 예는 src 서브디렉토리 안에 있는 모든 파일들을 컴파일하는데, 단 test*.ts 파일들을 컴파일에서 제외한다.

   {
       "compilerOptions": {
           "target": "ES6",
        },
        "include": [
           "src/*"
        ],
        "exclude": [
           "src/test*.ts"
        ],
   }
TypeScript 프로젝트 구조

아래는 TypeScript 프로젝트를 구성하는 방법을 예시한 것으로, TypeScript 소스코드를 ./src 폴더에 작성하고, (tsconfig.json 을 수정하여) JavaScript 출력 파일을 ./dist 폴더에 넣도록 한 후, TypeScript 프로젝트 전체를 컴파일(tsc)한 것이다. 일반적으로 큰 프로젝트의 경우 JavaScript 출력 파일을 별도의 output 폴더에 넣는 것이 편리하다.

TypeScript 네임스페이스 (namespace)

 

네임스페이스 (namespace)

네임스페이스는 클래스, 인터페이스, 함수, 변수 등을 논리적으로 묶는데 사용되는 것으로, C#의 네임스페이스와 비슷하다. 다만, C#에서는 함수, 변수가 namespace 바로 밑에 올 수 없지만, TypeScript에서는 그것이 가능하다. 네임스페이스를 정의하는 방법은 namespace 라는 키워드 뒤에 네임스페이스명을 적고, {...} 괄호 안에 클래스, 인터페이스, 함수, 변수 등을 넣으면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Helper 라는 네임스페이스를 정의
namespace Helper
{
    let counter: number = 0;
 
    function getNext(): number {
        return counter++;
    }
 
    class Converter {
        //...
    }
 
    interface IClone {
        //...
    }
}

네임스페이스 안에 있는 클래스, 인터페이스, 함수, 변수 등을 사용하기 위해서는 네임스페이스명을 앞에 붙여 사용하는데, 예를 들어 위의 예에서 Convert 클래스를 엑세스하기 위해서 "Helper.Converter" 와 같이 사용한다. 또한, 네임스페이스 안에 있는 클래스, 인터페이스, 함수, 변수 등은 디폴트로 그 네임스페이스 안에서만 사용할 수 있다. 아래 그림과 같이, 만약 네임스페이스 밖에서 사용하면 에러가 날 것이다.

네임스페이스 안의 클래스, 인터페이스, 함수, 변수 등을 네임스페이스 밖에서 혹은 다른 모듈에서 사용하고자 한다면, 해당 클래스, 인터페이스, 함수, 변수 앞에 export 를 붙여야 한다. 예를 들어, 아래와 같이 Converter와 IClone 앞에 export를 붙이면, namespace 밖에서 "Helper."을 붙여 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ns.ts
namespace Helper
{
    let counter: number = 0;
 
    function getNext(): number {
        return counter++;
    }
 
    export class Converter {
        //...
    }
 
    export interface IClone {
        //...
    }
}
 
let cvt = new Helper.Converter();
let itf: Helper.IClone;    

네임스페이스 안의 멤버를 export 하고, 만약 다른 모듈에서 사용하고자 한다면, 그 모듈은 Helper 네임스페이스를 인식하지 못하므로 아래와 같은 에러를 발생시킨다. (예: ns.ts 안의 코드를 ns2.ts 에서 사용한 경우)

위 문제를 해결하기 위해, 아래 예제와 같이 상단에 호출하고자 하는 네임스페이스를 갖는 모듈을 reference path에 지정하면 된다. 즉, 먼저 맨 앞에 /// 을 적고 reference path 뒤에 해당 모듈명을 적으면 된다.

1
2
3
/// <reference path="ns.ts" />
let cv = new Helper.Converter();
let it: Helper.IClone;        

JavaScript의 입장에서는 (디폴트로) 위의 ns.ts는 ns.js, ns2.ts는 ns2.js로 컴파일되는데, .js 를 사용하는 클라이언트(예: HTML)에서 ns.js 를 포함해야 nj2.js가 제대로 실행될 것이다. 그런데, 만약 TypeScript를 컴파일할 때, ns.ts와 ns2.ts를 묶어 하나의 .js를 생성한다면 더 편리할 수도 있다. 이를 위해 아래와 같은 tsc 명령을 사용하여, 복수 개의 .ts 파일을 컴파일해서 하나의 .js를 만들 수 있다.

    PS C:\> tsc --outFile mynj.js ns.ts ns2.ts