drei 是如何工作的
language server 为编辑器client提供语言智能提示功能,包括
- format
- validate
- autoComplete
- goToDefinition
- type Intelligence
等等功能。
vscode 官方及社区已有各种语言的 language server,为开发者提供了很多便利。
drei 是为 san 设计的 language server,(名字是 三 来的)。
一个标准的 language server 分为三层
- language server 对外提供标准化的接口
- language service 语言服务层面,在解析代码的基础上提供各种智能分析结果
- language service host 语言文件系统层,负责文件管理
对于一种复合语言,比如 html,其中除了 html tag,tag 中包含的内容也可能是一种独立的语言(js/ts/css)等, 这时 language server 分为四层
- language server
- language modes 在这一层,会根据请求的不同,调用不同语言的 language service,以提供正确的结果
- language services 不同块对应的语言的的 language service.
- language service host
对于 html 这种语言,其各部分语言之间的内容是不相关的:
而对于 .vue, .san 这种单文件组件,template中tag与script中声明的component相关,差值表达式与中script声明的data/methods相关,因此不再是一个简单的树状组织结构。
这个造型
vetur初期的版本基本上是在html的版本上,对script部分作了扩展以支持vue的api自动提示,对html部分增加的tag的自动提示,但对差值表达式没有进行处理。
drei在这个基础上更进一步,让template中的差值表达式和指令都能够获得language service的支持。
为了使用ts language service提供的能力,我们需要准备正确的ts文件,再把请求映射到这些生成的文件上。
由于 interpolation 中的变量是存在作用域的,因此从template的 interpolation 抽出一个 interpolation tree (类似于一个ast),再从这个树ts的 ast, 也就是一个 typescript 的 source file,。
ts的ast中所有的节点都包含位置信息,如果生成的ast位置信息不对,在进行后续操作时会抛出各种神秘的异常,这也是开发中的主要障碍。
另外,由于 san 设计的接口并没有考虑到 typescript 的局限性,ts language service 直接对 san文件的script部分进行推断基本就只能拿到一个any的结果,(这里也可能是因为我写的ts定义文件不对),因此在script部分的类型提示是从 js文件生成了一个ts文件,再从ts文件提供类型提示。
生成这两个新文件还有一个前提,需要知道原本script中用户声明的对象的类型:
对 ts language serivice 的扩展方式如这个图,如果获得类型和输出修改后的 sourcefile 的 language service 是同一个,你需要在访问一个文件之前知道他的内容,这里就会产生一个死循环。
因此这里使用了两个 language service
先从其中一个language service中获得基本的类型信息,再生成文件,给到第二个 language service 提供接口。