Vue3 所有函数源码即简要说明
文章目录
reactivity
compiler-core
compile.ts
baseCompile
baseCompile(template, options)
将 template 解析成 render 函数,重点步骤:
baseParse(template, options) 将字符串模板解析成 AST 对象。
transform(ast, …) 将 AST 进一步转换处理
将转换后的 ast 调用 codegen 的 generate 方法生成 render 。
|
|
这也是除了错误处理之后的完整的 baseCompile 函数实现。
getBaseTransformPreset
getBaseTransformPreset(prefixIdentifiers: boolean)
合并所有 transform,返回一个 TransformPreset
类型的数组
stage-2:
增加 transformBind 指令处理,处理 :class = "bar.baz"
的时候需要用到
|
|
stage-1: 01-simple text
第一阶段我们只需要文本转换,通过 用例一 即可,所以这里就只保留 transformText 就可以了,剩下的就是去实现它。
|
|
tranform.ts
transformExpression
transformExpression(node, context)
transformText
transformText(node, context)
该函数会返回一个用来转换文本节点类型(NodeTransform
)的函数。
返回函数分析(return () => { ... }
),主要由三个 for 构成:
第一个 for 嵌套第二个 for 构成双重循环,用来合并 node.children 里面相邻的文本 节点
第一个 For 里面使用的是 children.length 动态获取当前数组的长度,结合代码中的 splice 和 j–。从而完成合并操作。
1 2 3 4 5 6
// 1. 原来的 child 被重写 // 2. child, ` + `, next 合并到了新 child.children 里面 currentContainer.children.push(` + `, next); // 删除被合并的文本节点 children.splice(j, 1); j--; // -1 是因为上面删除了当前元素,for 循环过程中长度是动态获取的
第三个 for 遍历第一步之后的 children,对每个 child 进行重定义,类型改变成
NodeTyeps.TEXT_CALL
类型,增加 codegenNode 属性。
代码完整版:
|
|
使用到的外面函数和属性:
CREATE_TEXT: 一个符号属性
export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``);
createCallExpression(callee, args, loc) 返回 JS_CALL_EXPRESSION 类型对象。
PatchFlags 和 PatchFlagNames 一个名字映射
isText 文本节点类型(插值和 text)
1 2 3 4
export function isText(node) { // 插值或 text 均视为文本 return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT; }
对应的虚拟节点创建函数: createTextVNode
transform
transform(root, options)
调用的函数:
createTransformContext(root, options) 创建 transform 转换器类型的上下文对象
traverseNode(root, context) 遍历所有节点
ssr 服务端渲染处理
初始化 root 根节点上的一些属性
|
|
transformElement
transformElement(node, context)
stage-2
stage-1 03-inerpolation in pure div
|
|
进入 createVNodeCall 时的参数值:
这里会将一些需要用到的函数添加到 context.helpers:Set
中等待解构:
该用例中会有 CREATE_VNODE
被解构出来。
transformBind
transformBind(prop, node, context)
指令也属于属性一种,所以它的处理源头是在 transformElement 里面。
这里只不过是提供了 v-bind 处理方式。
|
|
transformIf()
这个函数是由一系列的操作之后才返回的一个函数,用来处理 /^(if|else|else-if)$/
指令,生成对应分支节点的 codegenNode
。
|
|
createCodegenNodeForBranch
createCodegenNodeForBranch(branch, index, context)
|
|
createChildrenCodegenNode
createChildrenCodegenNode(branch, index, context)
stage-1 05-interpolation, v-if, props
该阶段只完成一个孩子节点且是 ELEMENT 类型的时候处理,如果不是这种情况是需要用 fragment 将这些 children 包起来的。
|
|
createTransformContext
createTransformContext(root, options)
单纯的构建和初始化 transform 转换器上下文对象。
stage-1: 01 simple text
一些初始化的工作,并不需要具体实现什么,纯文本并没有用到。
|
|
stage-2: 02 pure interpolation 插值节点的编译
|
|
createRootCodegen
createRootCodegen(root, context)
创建 root 节点上的 codegenNode 值,这也是将来用来编译成 render 函数的源码字符串。
stage-1: 01 simple text
|
|
实现完这个之后发现,generate 里面的 genNode 还没实现,真实丢三落四~~~~。
createStructuralDirectiveTransform
createStructuralDirectiveTransform(name, fn)
|
|
traverseNode
traverseNode(node, context)
stage-1: 01 simple text 省略 switch 里面的上线,因为这里只是纯文本不再 case 范围。
|
|
stage-2: 02 pure interpolation 插值节点的编译
增加 INTERPOLATION 类型节点分支处理。
|
|
修改之后代码:
|
|
stage-3: 05-interpolation, v-if, props
增加 IF 和 IF_BRANCH 分支处理:
|
|
TODO createIfBranch(…)
traverseChildren
traverseChildren(parent, context)
处理 node.children 孩子节点。
|
|
遍历所有 ast ,让每个节点持有自父级引用。
遍历所有节点,进行 traverseNode,解析出 codegenNode 值
buildProps
buildProps(node, context, props = node.props, ssr = false)
stage-1 05-interpolation, v-if, props
|
|
hoistStatic
hoistStatic(root, context)
|
|
walk
walk(children, context, resultCache, doNotHoistNode = false)
|
|
processIf
processIf(node, dir, context, processCodegen)
处理 v-if/v-else/v-else-if 指令的函数。
|
|
codegen.ts
createCodgenContext(ast, context)
stage-1: 01 simple text
|
|
generate()
generate 函数雏形:
|
|
函数的目的是:通过 ast 来生成 code,这个 code 将来会被 compileToFunction 调用 new
Function(code)
生成 render 函数的。
stage-1: 01 simple text
|
|
代码中只包含文本解析需要的内容。但是结果显示:
ast: {type: 0, children: Array(1), loc: {…}, helpers: Array(0), components: Array(0), …} code: "function render(_ctx, _cache) {↵ with (_ctx) {↵ return null}}" map: ""
即: ast.codegenNode
是空值,最后并没有 执行 genNode(ast.codgenNode, context)
。
因此问题还在 transformText 里面,但是纯文本会直接在第一个 for 后的 if 判断中直接
return
了,那么问题出在哪???
进过往上追溯,发现在 traverseNode 实现中有一部分 switch 代码被我们省略,而里面就
有个 case 是文本节点会走到的,即: NodeTypes.ROOT
因为这个用例文本是直接挂在根
节点下面的,那么就得实现 traverseChildren 了。
然后,实现完 traverseChildren 之后并没解决问题,因为这里面根本没有处理赋值 codgenNode 的操作。
那么只能用最笨拙的方法了,直接搜索 codegen*
然后又发现新大陆(transform 里面有
个 createRootCodgen(…) 并没有实现), 🏃♂️ go ->
stage-2: 02 pure interpolation
这里新增了 push ast.helpers.map(...)
处理,比如 traverseNode stage-2 中新增的
INTERPOLATION 分支中的处理是 context.helper(TO_DISPLAY_STRING)
就是给上下文的
helpers 增加了 Symbol('toDisplayString')
。
|
|
正好在这里会检测 context.helpers
进行相应的处理。
traverseNode 的 switch 中插值 INTERPOLATION 分支处理中增加了一个
TO_DISPLAY_STRING 符号类型值到 context.helpers: Set
中,这其实就是个 _Vue
实例中的一个函数名称,在这里会遍历 context.helpers
将需要用到的函数从实例中解
构出来。
genNode(node, context)
stage-3: 04-interpolation in div with props
这个用例中需要解析 props[{ id }, { class }]
这两个属性经过 buildProps 处理之后
会变成一个对象结构:
{ type: 15 /* JS_OBJECT_EXPRESSION */, properties: [{id}, {class}], ...}
|
|
所以这里首先需要增加一个 JS_OBJECT_EXPRESSION
分支处理这两个属性,解析成属性对
象传递给 _createBlock('div', props, ...)
|
|
stage-2: 02 pure interpolation
这个阶段需要支持插值的解析,而插值在 ast 中数据结构为:
|
|
这里需要经过两次递归调用 genNode 分别去解析 type=5 // INTERPOLATION
和 type=4
// SIMPLE_EXPRESSION
两种类型,且前后形同父子关系。
那么就有:
|
|
先经过 INTERPOLATION 分支调用 genInterpolation(node, context) 去解析插值节点
stage-1: 01 simple text
这里我们只处理文本节点的情况:
|
|
然后就是实现 case 的 genText(node, context)
genNodeList(nodes, context, multilines=false, comma=true)
生成 _createBlock(tag, props, children, …) 函数的参数列表。
|
|
genNodeListAsArray(nodes, context)
将参数列表转成数组, nodes: [tag, props, chldren, ...]
-> ['div', {}, ...}]
|
|
genNullableArgs(args)
过滤掉参数列表尾部值为 假值 的参数。
|
|
genObjectExpression(node, context)
空属性列表,返回
{}
|
|
genExpressionAsPropertyKey(node, context)
生成对象属性名:
属性名由组合表达式动态生成,如:
{ [prefix + '_' + name]: 'value' }
静态属性,又分标准标识符和非标准的(需要用引号包起来的),如:
{ foo: 'value' }
或{ '23adf34': 'value' }
简单的动态属性,如:
{ [foo]: 'value' }
|
|
genCompoundExpression(node, context)
|
|
genText(node, context)
这里没什么阶段性的,就是一句很简单的字符串化文本节点内容。
|
|
genFunctionPreamble(ast, context)
生成
const _Vue = Vue
一些函数的全局提升解构,如:
const { createVNode: _createVnode } = _Vue
一些静态属性提升,如:
const _hoisted_1 = {key: 0}
|
|
genInterpolation(node, context)
|
|
context.helper(TO_DISPLAY_STRING)
是从 helpersMap 中取出 TO_DISPLAY_STRING 对
应的函数名称(下划线重命名之后的名称),看 context.helper
实现:
|
|
别名操作在 generate 的 useWithBlock 判断中生成。
genExpression(node, context)
|
|
表达式的值可以是静态的,也可以是动态的,
TODO 如果是静态直接字符串化???
DONE 如果是动态的直接 push content 变成变量直接从上下文去取变量值
如 02 pure interpolation 中的
world.burn()
会直接被塞到context.code
结合 成return _toDisplayString(world.burn());
生成函数之后相当于这样调 用:_ctx.world.burn()
。
genVNodeCall(node, context)
stage-1 03-inerpolation in pure div
增加 createVNode
helper
|
|
genCallExpression(node, context)
|
|
genConditionalExpression(node, context)
genHoists(ast.hoists, context)
scope id 处理
静态提升
const _hoisted_i = xxx
|
|
虚拟节点创建函数
name | transform | desc |
---|---|---|
createTextVNode | transformText | 创建文本虚拟节点 |