TIL 101 - Typescript DeepDive, LSP(Language Server Protocol)

김영현·2024년 6월 5일
0

TIL

목록 보기
112/129

What is Typescript

출처:https://learn.microsoft.com/en-us/shows/web-wednesday/what-is-typescript

Typescript is a Superset, still JS

자바스크립트지만, 자바스크립트의 상위 집합이다! 즉 자바스크립트를 래핑한 언어라고 볼 수 있다.
굳이 왜 사용할까?

동적 타입언어를 정적 타입언어로 바꾸어 타입을 안전하게사용하기 위함이다.

동적타입언어도 동적타입만의 장점이 있다. 변수선언시 간편하고, 배열(튜플)에 어떤 타입을 넣어도 된다.
그러나 협업의 관점에선 문제가 생긴다. 타인이 만든 코드의 타입을 하나하나 확인할 수도없다. 코드의 규모가 커지면 커질수록 생산성에 문제가 생긴다.

따라서 우리는 타입스크립트를 사용한다.


타입스크립트의 타입 검사

타입스크립트는 동적 언어인 자바스크립트를 정적 언어처럼 보이게 만든다. 하지만 정적 언어런타임 이전타입 검사를 실행해야한다.

이게 어떻게 가능한걸까?

타입스크립트 컴파일러

컴파일은 보통 명령어를 이용해서 컴파일을 진행한다. 타입스크립트도 마찬가지로 특정 명령어를 이용해 컴파일한다.

//hello.ts
let message: string = '안녕하세요'
console.log(message)

export default message //모듈오류 방지용 export

위 파일을 타입스크립트 컴파일러를 이용하여 컴파일해보자.

아래와 같은 결과를 받을 수 있다.(컴파일러 버전은 5.4.5임)

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var message = '안녕하세요';
console.log(message);
exports.default = message;

타입스크립트의 문법으로 작성되었던 .ts파일을 자바스크립트의 문법으로 작성된 .js파일로 변환해준다.
이게 타입스크립트 컴파일러가 하는 일이다.

그렇다면 어떤 과정을 거쳐서 ts => js 변환이 이루어지는걸까? 이전 TIL에서 배웠던 컴파일 과정을 기반으로 한 번 추측해보자.

  1. 소스코드를 통째로 받아온다.
  2. tokenization을 진행하여 syntax tree(parse tree)를 만든다.
  3. 2번을 참고하여 AST를 만든다
    3-1. AST는 최소한의 문법, 그렇다면 Parse Tree에서 타입을 체크하지 않을까? (추측)
  4. AST로부터 JS언어로 변환한다.
  5. Done!

실제로 위와 같은지 한 번 찾아보자.


출처 : https://www.huy.rocks/everyday/04-01-2022-typescript-how-the-compiler-compiles

  1. tsconfig.json파일을 읽어 컴파일 세팅값을 맞춘다.
  2. Parsertokenization하여 AST를 만든다. => parse tree가 아니라 왜 AST일까...?
  3. Binder라는 과정을 통해 AST의 노드들과 Symbol사이를 매핑한다. 이때 스코프, 식별자등을 매핑함
  4. Emitter를 호출하여 ASTJS코드 및 문자열로 변환하기위해 Emit Worker를 생성
    4-1. 이때 getDianostics()함수를 이용하여 타입도 체크함.
  5. AST 노드에서 3번에서 만든 매핑을 이용하여 코드를 분석함. 여기까지 과정이 잘 이루어졌다면 JS소스가 생성된다!

음! 원래 알고있던 컴파일 과정에서 조금씩 추가됐을 뿐, 전체적인 골자는 비슷하다.
다만 고유명사나 내부에서 쓰이는 함수, 기능들이 있어서 살짝 어려웠다.

vscode에서는 어떻게 타입체킹을 하는거지?

결국 컴파일과정에 타입 체킹이 들어있다. 하지만 개발자는 타입스크립트 파일을 실시간으로 컴파일하지 않는다.
😮...너무 당연한듯이 사용하고 있었다. 그러면 어떻게 실시간 타입 체킹을 지원하는걸까?

바로 LSP(Languages Server Protocol)을 이용하여 검사한다.

현대엔 다양한 에디터,IDE가 존재한다. 만약 자동완성을 지원한다고 가정해보자.
A편집기는 JAVA로 제작되어있다. B편집기는 C언어로 제작되어있다.
이때 JAVA자동완성이 필요하다고 해보자. A편집기는 쉽게 만들 수 있다. JAVA로 만들었기 때문이다. 하지만 B편집기는 C언어로 제작되었기에 C언어JAVA의 도메인모델을 구현해버리는 게 된다. (본말전도)

따라서 이러한 에디터가 기존 라이브러리를 재사용할 수 있다면, 풍부한 편집기능을 지원할때 보다 더 편리해 질 것이다.
이를 통합-추상화하여 만든 것이 LSP다.


출처 : https://www.eclipse.org/community/eclipse_newsletter/2017/may/article1.php

vscode가 언어 서버와 통신하며 자동완성, 오타, 타입체크 검출등의 역할을 수행한다.
그렇단 얘기는 인터넷에 연결되어있지 않으면 작업이 불가능 한걸까?


출처 : https://code.visualstudio.com/api/language-extensions/language-server-extension-guide

NO! 그렇지 않다. VSCODE 내부 99%의 LSP는 로컬 서버다. 따라서 인터넷에 연결되지 않아도 VSCODE가 자동완성, 오타검출, 정의로 이동 등 풍부한 편집기능을 제공해줄 수 있는 것이다!


잘 몰랐던 타입스크립트 기능들

타입스크립트 딥다이브 핸드북을 훑어보며 잘 몰랐던 타입스크립트의 기능들에 대해 알아보자.

declare

Declare: 선언하다

다른 곳에 존재하는(JS,Coffe Script, Browser, Node,...etc) JS코드를 사용하기 위해 declare라는 키워드를 사용한다.

foo = 123; // Error: 'foo' is not defined

//declare 사용

declare var foo: any;
foo = 123; //가능함

모듈패턴인 import, export대신 왜 이렇게 쓰는거지?🤔
=> 정확히는 실제 변수를 선언하는게 아니라, 컴파일러에게 선언된 타입이 존재한다라고 알려주는 것이다.

실제로 타입스크립트 내부 파일을 살펴보면

이렇게 개발자가 그냥 사용할 수 있는 값들인 NaN, Infinity등에 대해서 변수가 존재한다라고 declare를 통해 타입스크립트 컴파일러에게 알려주는 모습을 볼 수 있다.

보통 외부 API, 라이브러리를 활용할때 주로 사용하게 된다. 이러한 선언들을 ambient declare(주변 선언)라고 한다.

d.ts

declare typescript의 약자로 수많은 declare들이 모여있는 파일이다. 당연히 외부 라이브러리등이 타입스크립트를 지원하기위해 만든 파일이다.
단순히 declare만 되어있는 파일도 있고 type, interface등을 선언해놓은 파일도 있다.

이런식으로 명령어를 주게되면, d.ts파일을 손쉽게 만들 수 있다.

//hello.d.ts
declare let message: string;
export default message;

Enum, Const Enum, as const

Enumeration의 줄임말. 열거 가능한 자료형을 뜻한다. 다른 언어에는 꽤 있는데, JS에는 없다.
Enum의 특징은 뭘까?

enum Direction {
    Up,
    Down,
    Left,
    Right
}

이렇게 Direction이라는 Enum을 선언하면 자동으로 인덱스(0부터)가 할당된다.
이는 마치 JS에서 아래처럼 코드를 작성한 것과 같다.

let Dir;
(function (Dir) {
  Dir[(Dir["Up"] = 0)] = "Up";
  Dir[(Dir["Down"] = 1)] = "Down";
  Dir[(Dir["Left"] = 2)] = "Left";
  Dir[(Dir["Right"] = 3)] = "Right";
})(Dir || (Dir = {}));

console.log(Dir);
console.log(Dir.Up);
console.log(Dir[0]);

키-밸류 어느것으로 접근해도 반대 방향을 뱉는 자료구조구나? 참고로 인덱스(숫자)가 아닌 문자열을 대입할 수도 있다.

enum Name{
	Kim = 'CheolSoo',
  	Park = 'JiHun',
}

index signature

결론부터 말하자면 index signature문자열, 숫자만 가능하다.

js에서는 객체참조를 문자열로 할 수도 있다.

이때 문자열 대신 다른 객체를 넣는다면 JS런타임이 그 타입 내부 toString()를 강제로 호출한다.

위 내용으로 인하여 TS에서 index signature를 사용할 때 가끔 오류가 발생한다.

let obj = {
  toString() {
    return "Hello";
  },
};

let foo: any = {};
// 오류: 인덱스 서명은 string, number 여야 함...
foo[obj] = "World";

// FIX: TypeScript는 명시적으로 호출하게 강제함
foo[obj.toString()] = "World";

TS에서는 명시적으로 toString()을 호출하게 강제한다.이는 대개 객체의 기본toString()구현이 엉망이기 때문이다.

'object Object'는 어디서 많이 봤다 했더니, 객체를 문자열로 바꾸면 나타나는 현상이었구나🤣
참고로 숫자는 잘 동작한다.


느낀점

잘 몰랐던 부분들을 속시원하게 긁은 기분이다. 특히 declareindex sinagture은 애매모호했는데 확실히 알게되어 다행이다!
타입스크립트를 계속 사용하게 될텐데, 다음에 또 궁금했던점을 모아 포스팅 하는날이 오지않을까?

아마 다음 TIL은 MVC패턴 + node로 백엔드 or 간단한 nextjs 프로젝트가 될 것 같다.

profile
모르는 것을 모른다고 하기

0개의 댓글