编译器的核心技术
模板编译器
- 模板:特定结构的字符串、
- 编译器:一段将源代码翻译成目标代码的程序
- 完整的编译流程如下
Vue模板编译器
- 源代码:VUe组件模板
- 目标代码:js渲染函数
- Vue作为DSL,其编译流程会有所简化,如下所示
编译器的实现
- 通过parser解析器将模板转为AST,
- 通过transformer转换器将AST转换为JavaScript AST,
- 通过generator生成器将JavaScript AST生成渲染函数
Parser解析器
-
原理:通过有限状态自动机,按照一定的规则将字符串切割为一个个的token
-
有限状态自动机:自定义有限个状态,通过程序控制不断改变状态
-
token:词法标记,用于标记识别到的不同元素
- 模板字符串:“<p>Vue</p>”
- 自定义状态:tagOpen,tagName,text,tagEnd,tagEndName
- 自动改变状态:不断遍历模板字符串,若遇到'<'状态为tagOpen,遇到'p'状态为text等
- 切割为token:每次改变状态前,都将这一状态伴随的所有字符按状态类型整理为一个token
- tokens:[{type: 'tag', name: 'p'},{type: 'text', content: 'Vue'},{type: 'tagEnd', name: 'p'}]
-
AST:根据tokens构造AST
- 编译器的AST构造使用递归下降算法
- Vue的模板是用于描述HTML的,本身就互相嵌套,形成父子关系,天然的树形结构。AST正是抽象语法树,与其结构非常相似
- 遍历tokens,维护一个父级元素栈,为tokens的每项形成父子关系,构建出一个AST对象
- AST:
{ type: "root", children:[ { type: 'Element', tag: "p", children:[ { type: "Text", content: "Vue" } ] } ] }
Transformer转换器
- 本质:将AST转换为JS AST,AST是对Vue模板的描述,JS AST是对javascript渲染函数的描述
- 原理:为了描述渲染函数,我们深度遍历AST的每个节点,根据节点类型生成对应的h函数的描述
- 分类:FunctionDecl,ReturnStatement,CallExpression,StringLiteral,ArrayExpression
- 实现:根据AST的类型将其转换为以上分类对应类型,转换后的结果存储在AST节点的JSNode属性下
- 理解:并不需要重新建立父子关系,只需要将每个节点转换为渲染的函数,并存储即可
Generator生成器
- 原理:从AST获取JS AST,遍历每个JS AST节点,根据节点的类型生成对应的字符串,最后拼接起来。
- 分类:genFunctionDecl,genReturnStatement,gencallExpression,genStringLiteral,genArrayExpression
- 实现:如果是functionDecl,则拼接为 function(参数节点){} 此类形式即可
- 最终得到如下字符串:'function render(){return h('div',[h('p','vue')])}'