Eksploracja wnętrza kompilatora TypeScript
Kompilator TypeScript, często nazywany tsc
, jest jednym z podstawowych komponentów ekosystemu TypeScript. Transformuje kod TypeScript na JavaScript, wymuszając jednocześnie statyczne reguły typowania. W tym artykule zagłębimy się w wewnętrzne działanie kompilatora TypeScript, aby lepiej zrozumieć, jak przetwarza i transformuje kod TypeScript.
1. Proces kompilacji TypeScript
Kompilator TypeScript wykonuje szereg kroków, aby przekształcić TypeScript w JavaScript. Oto ogólny przegląd procesu:
- Parsowanie plików źródłowych do drzewa składni abstrakcyjnej (AST).
- Wiązanie i sprawdzanie typów AST.
- Wysyłanie kodu wyjściowego JavaScript i deklaracji.
Przyjrzyjmy się tym krokom bardziej szczegółowo.
2. Analizowanie kodu TypeScript
Pierwszym krokiem w procesie kompilacji jest parsowanie kodu TypeScript. Kompilator pobiera pliki źródłowe, parsuje je do AST i wykonuje analizę leksykalną.
Oto uproszczony widok tego, jak można uzyskać dostęp do pliku AST i nim manipulować za pomocą wewnętrznego interfejsu API języka TypeScript:
import * as ts from 'typescript';
const sourceCode = 'let x: number = 10;';
const sourceFile = ts.createSourceFile('example.ts', sourceCode, ts.ScriptTarget.Latest);
console.log(sourceFile);
Funkcja createSourceFile
służy do konwersji surowego kodu TypeScript na AST. Obiekt sourceFile
zawiera przeanalizowaną strukturę kodu.
3. Wiązanie i sprawdzanie typu
Po parsowaniu następnym krokiem jest powiązanie symboli w AST i przeprowadzenie kontroli typu. Ta faza zapewnia, że wszystkie identyfikatory są powiązane z odpowiednimi deklaracjami i sprawdza, czy kod przestrzega reguł typu TypeScript.
Sprawdzanie typu jest wykonywane przy użyciu klasy TypeChecker
. Oto przykład, jak utworzyć program i pobrać informacje o typie:
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));
}
});
W tym przykładzie TypeChecker
sprawdza typ deklaracji zmiennej i pobiera informacje o typie z AST.
4. Emisja kodu
Po zakończeniu sprawdzania typów kompilator przechodzi do fazy emisji. To tutaj kod TypeScript jest przekształcany w JavaScript. Wyjście może również zawierać pliki deklaracji i mapy źródłowe, w zależności od konfiguracji.
Oto prosty przykład użycia kompilatora do wyemitowania kodu 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.');
}
Funkcja program.emit
generuje dane wyjściowe JavaScript. Jeśli podczas emisji wystąpią jakieś błędy, zostaną one przechwycone i wyświetlone.
5. Wiadomości diagnostyczne
Jednym z kluczowych obowiązków kompilatora TypeScript jest dostarczanie programistom znaczących komunikatów diagnostycznych. Komunikaty te są generowane zarówno podczas fazy sprawdzania typu, jak i emisji kodu. Diagnostyka może obejmować ostrzeżenia i błędy, pomagając programistom szybko identyfikować i rozwiązywać problemy.
Oto jak pobrać i wyświetlić diagnostykę z kompilatora:
const diagnostics = ts.getPreEmitDiagnostics(program);
diagnostics.forEach(diagnostic => {
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
console.log(`Error ${diagnostic.code}: ${message}`);
});
W tym przykładzie diagnostyka jest wyodrębniana z programu i drukowana na konsoli.
6. Transformacja języka TypeScript za pomocą interfejsów API kompilatora
API kompilatora TypeScript pozwala programistom tworzyć niestandardowe transformacje. Możesz modyfikować AST przed emisją kodu, umożliwiając zaawansowane dostosowania i narzędzia do generowania kodu.
Oto przykład prostej transformacji, która zmienia nazwy wszystkich zmiennych na 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]);
Ta transformacja odwiedza każdy węzeł w AST i zmienia nazwy zmiennych w razie potrzeby.
Wniosek
Eksploracja wnętrza kompilatora TypeScript zapewnia głębsze zrozumienie tego, jak kod TypeScript jest przetwarzany i przekształcany. Niezależnie od tego, czy chcesz tworzyć niestandardowe narzędzia, czy też poprawić swoją wiedzę na temat działania TypeScript, zagłębianie się w wnętrze kompilatora może być oświecającym doświadczeniem.