You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
/** * From T, pick a set of properties whose keys are in the union K */typePick<T,KextendskeyofT>={[PinK]: T[P];};
复杂内容简单化,将 K extends keyof T 单独提取出来。
K extends keyof T 这里的含义是,K 包含在 keyof T 的键联合类型内。
从 T 中取出联合类型 K 的属性,并生成新的 type。
Omit
/** * Exclude from T those types that are assignable to U */typeExclude<T,U>=TextendsU ? never : T;/** * Construct a type with the properties of T except for those in type K. */typeOmit<T,Kextendskeyofany>=Pick<T,Exclude<keyofT,K>>;
准备
目录结构
在开始源码阅读前,需要掌握一些基本信息,如项目依赖,构建方式,配置文件等。
首先,先来了解下整个目录的大体结构:
. ├── __tests__/ ├── api-extractor.json ├── dist/ ├── index.js ├── node_modules/ ├── package.json └── src/
从以上目录及文件信息,我们可以得知如下信息:
estree-walker
、source-map
以及babel
;scripts
字段,说明由全局统一构建;了解了基本信息之后,我们先来编译一下
complier-core
部分:编译后,dist 目录内容如下:
了解了基本的项目结构后,我们再来了解下编译。
编译原理
这里以 Babel 为例,简单介绍下相关的编译原理:
Babel 采用 AST 的形式(Abstract Syntax Tree,抽象语法树)对 JavaScript 源代码进行处理。
具体工作流参照下图:
Babel 中不同的 package 完成不同的工作。
complier-core 概览
了解了 Babel 的大概原理,那我们再来看看 complier-core/src 中的文件:
看完目录,我们就基本能找到对应关系,也基本能了解每个 ts 文件的作用:
parse
等价于@babel/parser
transform
等价于@babel/traverse
codegen
等价于@babel/generator
我们把 Babel 的图替换下,得出下图:
这里我们来贴一段源码,大家就可以理解:
从上述代码中,我们可以看出
compiler-core
预留了options.nodeTransforms
,也就意味着 AST 转换部分支持自定义。大致了解了
complier-core
所做的事,那我们使用complier-core
来编译一段 vue template 的代码。编译
官方推出了
vue-next-template-explorer
供大家预览,所以这里我们使用此网站进行编译编译前:
编译后:
template 经过
complier-core
编译后,会被转换为 render 函数。了解了转换结果,我们开始正式的
complier
的学习。Parse 阶段 —— template -> AST
如图中所示,vue 的 template 模板会被转成 AST,这个过程对应代码中的
parse.ts
。接下来会分为两部分去分析 Parse,一是 TypeScript,二则是 Parse 的核心逻辑。
1.TypeScript
基础语法请参考 TS 官方文档,这里只讲解一些实用的内容。
先来看这样一个 type:
Required
其实很好理解,字面意思,就是必须的(必选项)。
其中
-?
为核心操作,将可选变为必选。与之对应的,是
Partial
, 将选项变为可选。除此之外,
keyof
有必要介绍下。keyof
keyof
有点像Object.keys
,会取出interface
中的所有 key,并产生联合类型。Pick
复杂内容简单化,将
K extends keyof T
单独提取出来。K extends keyof T
这里的含义是,K 包含在keyof T
的键联合类型内。从 T 中取出联合类型 K 的属性,并生成新的 type。
Omit
其中
Exclude
代表移除掉 T 中 U 相关的属性。Omit
则为移除 T 中联合类型 K 的属性,并生成新的 type。解释 MergedParserOptions
其实简单来说,
interface ParserOptions
中与联合类型OptionalOptions
所对应的属性为可选项,而除了联合类型OptionalOptions
外的属性为必填项。验证
下方源码中为默认的 parser 选项,除了
isNativeTag
和isBuiltInComponent
以外,均为默认值。以上是
parse.ts
文件中稍微高级一些的 ts 用法,用到了 Utility Types。2.Parse 核心逻辑
在学习核心逻辑之前,我们看看 vue-next 是如何对编译器进行调试的。
本地调试
在文章开始时,我们提到了 vue-next-template-explorer,这个工具除了给大家学习参考外,也是编译器的调试工具。
翻看源码时,发现了 template-explorer 的启动命令
接下来,我们就可以对源代码为所欲为了~
核心逻辑
我们继续沿用,文章开始的例子(因为例子中包含了 if 和 for):
我们先找到 Parse 的主函数
baseParse
:我们在最开始已经了解了 template 在 parse 阶段,会被编译成 AST。
由此可以得知,上述代码中 root 为解析后的 AST 对象,其类型为 RootNode。
AST 本质上就是一个 JSON 对象,让我们来看看上述 template 的 AST 的基本结构:
baseParse 中调用了 5 个函数:
createParserContext
此函数创建了一个 context,用于关联上下文保存数据。
getCursor
cursor 可以理解为对每个节点加了一个下标,此方法用于获取上下文中 cursor 的值。
cusor 由 column、line 以及 offset 组成。
createRoot
其含义是,创建 AST JSON 的根。
大家可以理解为每个 template 的根都是一样的。
参数为
children
和loc
getSelection
parseChildren
此函数为核心处理逻辑。(最重要的放在最后)
大家在大学时,都学过树的遍历方式。
这里 AST 的本质就是一颗树,因此上述遍历方式均有效。
那如果将
template
-> AST,会如何做?比如,这个例子:
抛开自动闭合、注释、属性、指令及插值等特性,简化版的 AST 如下:
对源码进行逐行处理,根据
<
和</
来判断是节点开始,还是节点结束。逐行解析。
vue-next 在解析时,处理了几种文本类型:
<textarea>
<style>
,<script>
<![CDATA[]]>
以注释代替代码:
参考资料
The text was updated successfully, but these errors were encountered: