原文地址:TypeScript/How the compiler compiles (huy.rocks)
本文灵感源自How the TypeScript compiler compiles,你可以通过观看此视频深入理解TypeScript的编译过程。
宏观上来说,TypeScript编译器(compiler)是一个帮助我们分析和编译TypeScript代码的工具。它将TypeScript代码转换为JavaScript代码(.js),类型声明文件(.d.ts)和source map文件(*.js.map)
如果在源文件中存在错误,TypeScript编译器也会提供一些诊断信息,据此我们就能知道代码中发生了哪些错误,并且该如何修复它们。
1. 编译过程
编译器内部由不同的部分组成,具有非常复杂编译的流程,下图是对这些过程的总结:
当你输入tsc
命令后,编译器便开始了整个编译流程。在编译前,TypeScript编译器需要读取tsconfig.json
文件,该文件实际上定义了两个部分:编译器选项(Compiler Options) 和输入的文件(Input Files) 。
{
"files": [
"src/*.ts"
],
"compilerOptions": {
...
}
}
编译上下文会被创建为Program对象,该对象在src/compiler/program.ts中定义。当Program对象创建完毕,紧接着它会加载所有输入文件(input files)和它们相关的依赖(their imports)。之后会调用Parser(src/compiler/parser.ts)将文件转换为 AST(Abstract Syntax Tree,抽象语法树)。
更底层上讲,Parser会创建一个Scanner实例(src/compiler/scanner.ts),它会扫描源代码并且生成SyntaxKind
标记的流(stream of SyntaxKind
tokens)。
解析没有就此结束,在此之后,AST会被交给Binder(src/compiler/binder.ts),创建一个AST Nodes和Symbols的映射。
每个Symbol都存储着对应Node类型信息的元数据,Binder创建的Symbols Table将会在之后的阶段(类型检查)被使用。
此后,随着Program.emit
被调用,将会创建Emit Worker,并且由它来将AST转换为JavaScript代码字符串和其他一些东西。有两种Emitter类型,分别是:
- JavaScript Emitter:定义在src/compiler/emitter.ts,生成JavaScript代码和Source Map文件。
- Type Definition Emitter:定义在src/compiler/definitionEmitter.ts,生成类型定义文件。
当Emitter运行时会调用getDiagnostics()
方法,用来创建Type Checker(src/compiler/checker.ts)。然后Emitter会遍历AST以处理每个Node。
每个Node都会使用Symbols Table中的类型数据进行代码分析,如果一切顺利,最终会生成对应的JavaScript代码。
2. 错误报告
编译阶段返回的错误取决于编译器发现错误的阶段。
enum BuildResultFlags {
None = 0,
Success = 1 << 0,
DeclarationOutputUnchanged = 1 << 1,
ConfigFileErrors = 1 << 2,
SyntaxErrors = 1 << 3,
TypeErrors = 1 << 4,
DeclarationEmitErrors = 1 << 5,
EmitErrors = 1 << 6,
AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors | EmitErrors
}
例如,如果tsconfig.json
存在错误,将会返回ConfigFileErrors
。
如果错误由Scanner触发,那么返回就是SyntaxErrors
。有时代码语法正确,但是存在语义上的错误,大多数情况会返回TypeErrors
,该错误一般由Parser或Type Checker捕获,例如:
let a: number = "hello";
该代码语法正确,但是存在语义错误,因为你不能将一个字符串类型赋值给数值类型的变量。
3. 总结
本篇文章仅概述了整个编译过程,还有各个部分之间的关系。至此,你可以深入TypeScript源码,去探究每个过程的具体实现。
我推荐阅读TypeScript Compiler Internals,它是这篇文章的深入版(它深入了每个部分的代码,并且讲解了彼此调用时所发生的事情)。