TypeScript 架构概述

本文首先介绍 TS 架构的各个组成,而后是涉及的数据结构,最后会介绍整个编译过程。git

原文: Architectural Overview

架构分层

TS 架构分层

TS 架构层次如上图所示,下面将对每层进行分析。github

核心编译器

核心编译器位于最底层,它包含如下部分:typescript

  • 语法解析器(Parser):根据 TS 语法,从一系列源文件生成对应的抽象语法树(AST)。
  • 类型联合器(Binder):合并同一类型名称的全部声明,例如在不一样文件中的同名接口,这使得类型系统能够直接使用合并后的类型。
  • 类型检查器(Checker):解析每种类型结构,检查语义并生成恰当的检查结果。
  • 代码生成器(Emitter):把 .ts.d.ts 文件转换成 .js.d.ts.map 等文件。
  • 预处理器(Pre-processor):编译上下文(Compilation)指的是和程序相关的全部文件。编译器会按序检查全部传入的待编译入口文件,而后会把这些文件中直接或间接 import 的文件,以及 /// <reference path=... /> 指向的文件,归入到编译过程,从而构成了最终的编译上下文。经过遍历文件索引图,会获得一个已排序的源文件列表,这些文件列表就构成了整个应用程序。在解析 import 时,编译器会优先查找 .ts.d.ts 文件,以确保处理的是最新的文件。编译器默认使用跟 Node.js 相似的模块定位方式,它会逐路径往上查找能匹配到指定模块名的 .ts.d.ts 文件。若是没有定位到对应的模块,编译器也不必定抛出错误,由于该模块可能在环境模块中被声明了,好比 path 等 Node.js 内置模块。

独立编译器

独立编译器在核心编译器的基础上额外提供了批量编译命令,它能针对不一样引擎(如 Node.js)采起不一样的文件读写策略。咱们经过 npm i typescript -g 后,得到的 tsc 命令实际就是这个独立编译器。它会处理咱们命令行中指定的文件,而后送入核心编译器进行编译。npm

语言服务

语言服务为核心编译器封装了一层接口,尤为适用于编辑器一类的应用。
语言服务支持典型的编辑器操做,包括:服务器

  1. 自动补全、函数签名提示、格式化和高亮、着色等
  2. 基本重构功能,如重命名
  3. 调试接口助手,如断点验证
  4. TS 特有的增量编译(--watch

语言服务被设计用来专门处理这样的场景:在长时间存在的编译上下文中,源文件会随着时间不断的变化。
从这个角度来讲,相比于市面上的其余编译器接口,语言服务在对待程序和源码的处理方式上提供了较为不一样视角。数据结构

附: 详细的语言服务 API 使用文档

独立服务器

独立服务器 tsserver 对编译器和语言服务层进行了封装,对外暴露了一种基于 JSON 协议的接口,称为语言服务协议(LSP)。架构

附:详细的独立服务器 文档

VS Code 就是一个典型的使用语言服务的编辑器,它经过 LSP 来和语言服务通讯,从而实现良好的编码体验。编辑器

数据结构

TS 编译器中使用到的主要数据结构有如下 6 类:函数

  • Node : AST 的基本构建单元块。一般来讲,Node 表明了语法中的非终端节点。与非终端节点相对的终端节点,好比标识符、字面量等,也在 AST 中。
  • SourceFile :对应源文件的 AST 。SourceFile 自己是一个 Node,它额外提供了一些接口,用于访问包括原始文本、文件包含的引用、标识符列表,以及字符位置映射。
  • Program :编译单元的全部 SourceFile 和编译选项的集合。它是类型系统和代码生成的主要入口。
  • Symbol :已命名的声明,由类型联合器所生成。它链接了 AST 中的声明节点和其余地方的同名声明实体。它是语义系统的基本构建单元块。
  • Type : 它是语义系统的另外一部分,它能够是具名的(如类、接口),也能够是匿名的(如对象字面量)。
  • Signature : TS 语言中包含三种类型签名:函数调用签名、构造函数签名和索引签名。

编译过程概述

TS Compiler

整个编译过程从预处理开始。编码

1、 预处理器会找出全部 import 语句 和 reference 指令所依赖的文件,并把它们都列为待编译文件。

2、 解析器解析全部待编译文件,生成 AST Node 。这仅仅是以树的形式来抽象表示待编译文件。SourceFile 对象除了是表示文件的 AST 外,还有额外的信息,好比文件名、源码等。不过此时的 SourceFile 并无包含类型信息。

3、 类型联合器遍历 AST ,生成并绑定 Symbol 。每一个具名实体类型都会建立一个 Symbol。要注意的是,不一样的多个声明节点可能有相同的类型名称。这就意味着不用的 Node 能够有相同的 Symbol,每一个 Symbol 会跟踪全部跟它有关的 Node 。举例来讲,对于相同名称的 classinterface ,它们的类型会合并,而且指向相同的 Symbol 。类型联合器也会处理好做用域,以确保每一个 Symbol 处于正确的做用域范围内。

4、 生成 Symbol 以后,经过调用 createSourceFile 就能够生成具备 SymbolSourceFile 了。不过,Symbol 表示的是单个文件中的具名实体类型,因为来自多个文件的同名类型声明能够合并,所以下一步须要经过 Program 对象来构建一个囊括全部文件的全局 Symbol 视图。

5、 Program 使用 createProgram 接口生成,它包括全部 SourceFile 以及 CompilerOptions

6、 针对 Program 建立一个 TypeChecker ,它是 TS 类型系统的核心。它主要负责理清来自多个文件的 Symbol 之间的关系,绑定 TypeSymbol,以及生成语义诊断信息(好比错误信息)。具体来讲,TypeChecker 作的第一件事是把来自不一样 SourceFileSymbol 整合到单个视图中,而后会建立一张 Symbol 表,用来记录全部的 Symbol ,来自不一样文件的同名 Symbol 会在这个记录过程当中完成合并。一旦 TypeChecker 完成初始化,它就能够处理关于当前 Program 的任何类型问题了,好比:

* 某个 `Node` 的 `Symbol` 是什么?
* 某个 `Symbol` 的 `Type` 是什么?
* AST 的某个局部有哪些 `Symbol` 是可见的?
* 某个函数声明有哪些可用的 `Signature` ?
* 某个文件应该报告哪些错误信息?

TypeChecker 的全部检查都是延迟计算的。若是问它一个问题,它只会检查与这个问题相关的必要信息。也就是说,它会只检查与当前问题相关的 NodeSymbolType ,而不会尝试检查额外的信息。

7、 最后,针对 Program 也会建立一个代码生成器,它负责针对给定的 SourceFile 生成预期的代码文件,包括 .js.jsx.d.ts.js.map 文件。

相关文章
相关标签/搜索