TypeScript의 컴파일러 내부 탐색

TypeScript의 컴파일러는 종종 tsc이라고도 하며, TypeScript 생태계의 핵심 구성 요소 중 하나입니다. 정적 타이핑 규칙을 적용하면서 TypeScript 코드를 JavaScript로 변환합니다. 이 글에서는 TypeScript 컴파일러의 내부 작동 방식을 살펴보고 TypeScript 코드를 처리하고 변환하는 방법을 더 잘 이해해 보겠습니다.

1. TypeScript 컴파일 프로세스

TypeScript 컴파일러는 TypeScript를 JavaScript로 변환하기 위해 일련의 단계를 따릅니다. 다음은 프로세스에 대한 개략적인 개요입니다.

  1. 소스 파일을 AST(추상 구문 트리)로 구문 분석합니다.
  2. AST 바인딩 및 유형 검사.
  3. 출력 JavaScript 코드와 선언을 내보냅니다.

이러한 단계를 더 자세히 살펴보겠습니다.

2. TypeScript 코드 구문 분석

컴파일 프로세스의 첫 번째 단계는 TypeScript 코드를 구문 분석하는 것입니다. 컴파일러는 소스 파일을 가져와 AST로 구문 분석하고 어휘 분석을 수행합니다.

다음은 TypeScript의 내부 API를 사용하여 AST에 액세스하고 조작하는 방법을 간략하게 나타낸 것입니다.

import * as ts from 'typescript';

const sourceCode = 'let x: number = 10;';
const sourceFile = ts.createSourceFile('example.ts', sourceCode, ts.ScriptTarget.Latest);

console.log(sourceFile);

createSourceFile 함수는 원시 TypeScript 코드를 AST로 변환하는 데 사용됩니다. sourceFile 객체는 코드의 구문 분석된 구조를 포함합니다.

3. 바인딩 및 유형 검사

구문 분석 후 다음 단계는 AST의 심볼을 바인딩하고 유형 검사를 수행하는 것입니다. 이 단계에서는 모든 식별자가 해당 선언에 연결되어 있는지 확인하고 코드가 TypeScript의 유형 규칙을 따르는지 확인합니다.

유형 검사는 TypeChecker 클래스를 사용하여 수행됩니다. 다음은 프로그램을 만들고 유형 정보를 검색하는 방법의 예입니다.

const program = ts.createProgram(['example.ts'], {});
const checker = program.getTypeChecker();

// Get type information for a specific node in the AST
sourceFile.forEachChild(node => {
    if (ts.isVariableStatement(node)) {
        const type = checker.getTypeAtLocation(node.declarationList.declarations[0]);
        console.log(checker.typeToString(type));
    }
});

이 예에서 TypeChecker는 변수 선언의 유형을 확인하고 AST에서 유형 정보를 검색합니다.

4. 코드 방출

타입 검사가 완료되면 컴파일러는 방출 단계로 넘어갑니다. 여기서 TypeScript 코드가 JavaScript로 변환됩니다. 구성에 따라 출력에는 선언 파일과 소스 맵도 포함될 수 있습니다.

다음은 컴파일러를 사용하여 JavaScript 코드를 내보내는 방법의 간단한 예입니다.

const { emitSkipped, diagnostics } = program.emit();

if (emitSkipped) {
    console.error('Emission failed:');
    diagnostics.forEach(diagnostic => {
        const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
        console.error(message);
    });
} else {
    console.log('Emission successful.');
}

program.emit 함수는 JavaScript 출력을 생성합니다. 방출 중에 오류가 있으면 캡처하여 표시합니다.

5. 진단 메시지

TypeScript 컴파일러의 주요 책임 중 하나는 개발자에게 의미 있는 진단 메시지를 제공하는 것입니다. 이러한 메시지는 유형 검사 및 코드 방출 단계 모두에서 생성됩니다. 진단에는 경고 및 오류가 포함될 수 있으므로 개발자가 문제를 신속하게 식별하고 해결하는 데 도움이 됩니다.

컴파일러에서 진단 결과를 검색하고 표시하는 방법은 다음과 같습니다.

const diagnostics = ts.getPreEmitDiagnostics(program);

diagnostics.forEach(diagnostic => {
    const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
    console.log(`Error ${diagnostic.code}: ${message}`);
});

이 예에서는 진단 결과가 프로그램에서 추출되어 콘솔에 인쇄됩니다.

6. 컴파일러 API를 사용한 TypeScript 변환

TypeScript 컴파일러 API를 사용하면 개발자가 사용자 정의 변환을 만들 수 있습니다. 코드 방출 전에 AST를 수정하여 강력한 사용자 정의 및 코드 생성 도구를 사용할 수 있습니다.

다음은 모든 변수의 이름을 newVar으로 바꾸는 간단한 변환의 예입니다.

const transformer = (context: ts.TransformationContext) => {
    return (rootNode: T) => {
        function visit(node: ts.Node): ts.Node {
            if (ts.isVariableDeclaration(node)) {
                return ts.factory.updateVariableDeclaration(
                    node,
                    ts.factory.createIdentifier('newVar'),
                    node.type,
                    node.initializer
                );
            }
            return ts.visitEachChild(node, visit, context);
        }
        return ts.visitNode(rootNode, visit);
    };
};

const result = ts.transform(sourceFile, [transformer]);
console.log(result.transformed[0]);

이 변환은 AST의 각 노드를 방문하고 필요에 따라 변수의 이름을 바꿉니다.

결론

TypeScript의 컴파일러 내부를 탐색하면 TypeScript 코드가 처리되고 변환되는 방식에 대한 더 깊은 이해가 제공됩니다. 사용자 지정 도구를 빌드하거나 TypeScript 작동 방식에 대한 지식을 향상시키려는 경우 컴파일러의 내부를 파헤치는 것은 계몽적인 경험이 될 수 있습니다.