当前位置: 首页 > Web前端 > HTML

Vue3模板编译原理

时间:2023-03-28 17:13:32 HTML

模板编译流程Vue3模板编译就是将模板字符串编译成渲染函数//template

{{LH_R}}

//renderimport{toDisplayStringas_toDisplayString,createElementVNodeas_createElementVNode,openBlockas_openBlock,createElementBlockas_createElementBlock}from"vue"导出函数render(_ctx,_cache,$props,$setup,$data,$options){return(_openBlock(),_createElementBlock("div",null,[_createElementVNode("p",null,_toDisplayString(_ctx.LH_R),1/*TEXT*/)]))}我会按照编译过程分3步分析parse:converttemplatestringtotemplateASTtransform:converttemplateASTtoASTgeneratethat描述渲染函数:生成基于AST的渲染函数baseParse(template,options):templateconst[nodeTransforms,directiveTransforms]=getBaseTransformPreset(prefixIdentifiers)转换(ast,extend({},options,{prefixIdentifiers,nodeTransforms:[...nodeTransforms,...(options.nodeTransforms||[])//用户转换],directiveTransforms:extend({},directiveTransforms,options.directiveTransforms||{}//用户转换)}))returngenerate(ast,extend({},options,{prefixIdentifiers}))}parseparse遍历模板字符串,然后循环判断开始标签和结束标签将字符串拆分成token。有一个tokenlist,然后scantokenlist维护一个startlabelstack。每当扫描到起始标签节点时,它就会被推到堆栈的顶部。堆栈顶部的节点始终是下一个扫描节点的父节点。这样,当所有的Token都扫描完后,就可以构建一个树形的AST。下面是parseChildren源码的简化版,是parse的主要入口。functionparseChildren(context:ParserContext,mode:TextModes,ancestors:ElementNode[]//节点栈结构,用于维护节点嵌套关系):TemplateChildNode[]{//获取父节点constparent=last(ancestors)constns=父母?parent.ns:Namespaces.HTMLconstnodes:TemplateChildNode[]=[]//存储已解析的AST子节点//遇到结束标记时结束解析while(!isEnd(context,mode,ancestors)){//剪切模板字符串consts=context.sourceletnode:TemplateChild节点|模板子节点[]|undefined=undefinedif(mode===TextModes.DATA||mode===TextModes.RCDATA){if(!context.inVPre&&startsWith(s,context.options.delimiters[0])){//解析插值表达式{{}}node=parseInterpolation(context,mode)}elseif(mode===TextModes.DATA&&s[0]==='<'){if(s[1]==='!'){//解析注释节点和文档声明...}elseif(s[1]==='/'){if(s[2]==='>'){//对于自闭合标签,提前三个字符advanceBy(context,3)continue}elseif(/[a-z]/i.test(s[2])){//解析结束标签parseTag(context,TagType.End,parent)continue}else{//如果不满足以上条件,则解析为伪注释node=parseBogusComment(context)}}elseif(/[a-z]/i.test(s[1])){//解析html开始label,获取解析后的AST节点node=parseElement(context,ancestors)}}}if(!node){//普通文本节点node=parseText(context,mode)}//如果节点是数组,则遍历并添加到节点中if(isArray(node)){for(leti=0;i

LH_R

模板为例,将div开始标签放在stack,context.source=

LH_R

,ancestors=[div]p开始标签入栈,context.source=LH_R

,ancestors=[div,p]parsetextLH_R解析p结束标签,p标签出栈,解析div结束标签,div标签出栈,栈为空,解析模板。transformtransform以深度优先的方式遍历AST。在遍历过程中,对节点的操作和转换采用插件式架构,封装成一个独立的函数,然后通过context.nodeTransforms注册转换函数。转换过程是先转换子节点,因为有些父节点的转换依赖于子节点。对当前节点进行节点转换。遍历完所有子节点后,执行相应命令的onExit回调退出转换*/exportfunctiontraverseNode(node:RootNode|TemplateChildNode,context:TransformContext){//记录当前遍历的节点context.currentNode=node/*nodeTransforms:transformElement、transformExpression、transformText...transformElement:负责整个节点层级的变换transformExpression:负责节点中表达式的变换transformText:负责节点中文本的变换*/const{nodeTransforms}=contextconstexitFns=[]//依次调用变换工具for(leti=0;i