Verkenning van de compiler-interne werking van TypeScript
De compiler van TypeScript, vaak aangeduid als tsc
, is een van de kerncomponenten van het TypeScript-ecosysteem. Het transformeert TypeScript-code naar JavaScript en handhaaft daarbij statische typeregels. In dit artikel duiken we in de interne werking van de TypeScript-compiler om beter te begrijpen hoe het TypeScript-code verwerkt en transformeert.
1. Het TypeScript-compilatieproces
De TypeScript-compiler volgt een reeks stappen om TypeScript om te zetten in JavaScript. Hier is een overzicht op hoog niveau van het proces:
- De bronbestanden parseren in een Abstract Syntax Tree (AST).
- Binden en typecontrole van de AST.
- De uitvoer-JavaScript-code en -declaraties uitzenden.
Laten we deze stappen eens nader bekijken.
2. TypeScript-code parsen
De eerste stap in het compilatieproces is het parsen van de TypeScript-code. De compiler neemt de bronbestanden, parseert ze in een AST en voert lexicale analyse uit.
Hier is een vereenvoudigde weergave van hoe u toegang krijgt tot de AST en deze kunt bewerken met behulp van de interne API van 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);
De functie createSourceFile
wordt gebruikt om onbewerkte TypeScript-code om te zetten in een AST. Het object sourceFile
bevat de geparseerde structuur van de code.
3. Binding en typecontrole
Na het parsen is de volgende stap het binden van de symbolen in de AST en het uitvoeren van type-checking. Deze fase zorgt ervoor dat alle identifiers gekoppeld zijn aan hun respectievelijke declaraties en controleert of de code de typeregels van TypeScript volgt.
Type-checking wordt uitgevoerd met de klasse TypeChecker
. Hier is een voorbeeld van hoe u een programma maakt en type-informatie ophaalt:
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));
}
});
In dit voorbeeld controleert de TypeChecker
het type van een variabeledeclaratie en haalt type-informatie op uit de AST.
4. Code-emissie
Zodra de typecontrole is voltooid, gaat de compiler door naar de emissiefase. Dit is waar de TypeScript-code wordt omgezet in JavaScript. De uitvoer kan ook declaratiebestanden en bronkaarten bevatten, afhankelijk van de configuratie.
Hier is een eenvoudig voorbeeld van hoe u de compiler kunt gebruiken om JavaScript-code uit te zenden:
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.');
}
De functie program.emit
genereert de JavaScript-uitvoer. Als er fouten optreden tijdens de emissie, worden deze vastgelegd en weergegeven.
5. Diagnostische berichten
Een van de belangrijkste verantwoordelijkheden van de TypeScript-compiler is het leveren van zinvolle diagnostische berichten aan de ontwikkelaar. Deze berichten worden gegenereerd tijdens zowel type-checking als code-emissiefases. De diagnostiek kan waarschuwingen en fouten bevatten, waardoor ontwikkelaars problemen snel kunnen identificeren en oplossen.
Hier leest u hoe u diagnostische gegevens van de compiler kunt ophalen en weergeven:
const diagnostics = ts.getPreEmitDiagnostics(program);
diagnostics.forEach(diagnostic => {
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
console.log(`Error ${diagnostic.code}: ${message}`);
});
In dit voorbeeld worden de diagnosegegevens uit het programma gehaald en naar de console afgedrukt.
6. TypeScript transformeren met compiler-API's
Met de TypeScript compiler API kunnen ontwikkelaars aangepaste transformaties maken. U kunt de AST aanpassen vóór code-emissie, wat krachtige aanpassingen en codegeneratietools mogelijk maakt.
Hier is een voorbeeld van een eenvoudige transformatie die alle variabelen hernoemt naar 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]);
Deze transformatie bezoekt elk knooppunt in de AST en hernoemt variabelen indien nodig.
Conclusie
Door de compiler internals van TypeScript te verkennen, krijgt u een dieper inzicht in hoe TypeScript-code wordt verwerkt en getransformeerd. Of u nu aangepaste tools wilt bouwen of uw kennis van de werking van TypeScript wilt verbeteren, het kan verhelderend zijn om u te verdiepen in de compiler internals.