Appearance
遍历AST
语法树
我们需要遍历ast
语法树,访问树中节点进行语法树的转化
function transformElement(node){
console.log('元素处理',node)
}
function transformText(node){
console.log('文本处理',node)
}
function transformExpression(node){
console.log('表达式')
}
function traverseNode(node,context){
context.currentNode = node;
const transforms = context.nodeTransforms;
for(let i = 0; i < transforms.length;i++){
transforms[i](node,context); // 调用转化方法进行转化
if(!context.currentNode) return
}
switch(node.type){
case NodeTypes.ELEMENT:
case NodeTypes.ROOT:
for(let i = 0; i < node.children.length;i++){
context.parent = node;
traverseNode(node.children[i],context);
}
}
}
function createTransformContext(root){
const context = {
currentNode:root, // 当前转化节点
parent:null, // 当前转化节点的父节点
nodeTransforms:[ // 转化方法
transformElement,
transformText,
transformExpression
],
helpers: new Map(), // 创建帮助映射表,记录调用方法次数
helper(name){
const count = context.helpers.get(name) || 0;
context.helpers.set(name,count+1)
return name
}
}
return context
}
function transform(root){
// 创建转化的上下文, 记录转化方法及当前转化节点
let context = createTransformContext(root)
// 递归遍历
traverseNode(root,context)
}
export function compile(template){
const ast = baseParse(template);
transform(ast);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
退出函数
表达式不需要退出函数,直接处理即可。元素需要在遍历完所有子节点在进行处理
function transformExpression(node){
if(node.type == NodeTypes.INTERPOLATION){
console.log('表达式')
}
}
function transformElement(node){
if(node.type === NodeTypes.ELEMENT ){
return function postTransformElement(){ // 元素处理的退出函数
// 如果这个元素
console.log('元素',node)
}
}
}
function transformText(node){
if(node.type === NodeTypes.ELEMENT || node.type === NodeTypes.ROOT){
return ()=>{
console.log('元素/root',node)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function traverseNode(node,context){
// ...
for(let i = 0; i < transforms.length;i++){
let onExit = transforms[i](node,context); // 调用转化方法进行转化
if(onExit){
exitsFns.push(onExit)
}
if(!context.currentNode) return
}
// ...
// 最终context.currentNode 是最里面的
context.currentNode = node; // 修正currentNode;
let i = exitsFns.length
while (i--) {
exitsFns[i]()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
转化表达式
runtimeHelpers.ts
export const TO_DISPLAY_STRING = Symbol(`toDisplayString`);
export const helperNameMap = {
[TO_DISPLAY_STRING]: "toDisplayString",
}
1
2
3
4
2
3
4
switch(node.type){
case NodeTypes.INTERPOLATION:
context.helper(TO_DISPLAY_STRING); // 用于JSON.stringify
break
// ...
}
1
2
3
4
5
6
2
3
4
5
6
最后生成的代码我们取值时需要从上下文中进行取值。
export function transformExpression(node){
if(node.type == NodeTypes.INTERPOLATION){
node.content.content = `_ctx.${node.content.content}`; // 修改content信息
}
}
1
2
3
4
5
2
3
4
5
转化文本元素
文本元素的转化我们需要将多个文本连接起来,如果是动态文本添加patchFlags
标识,最终生成文本调用逻辑
function isText(node) {
return node.type == NodeTypes.INTERPOLATION || node.type == NodeTypes.TEXT;
}
export function transformText(node,context){
if(node.type === NodeTypes.ELEMENT || node.type === NodeTypes.ROOT){
return ()=>{
// 如果这个元素
let hasText = false;
const children = node.children;
let currentContainer = undefined // 合并儿子
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (isText(child)) {
hasText = true;
for (let j = i + 1; j < children.length; j++) {
const next = children[j];
if(isText(next)){
if(!currentContainer){
currentContainer = children[i] = { // 合并表达式
type:NodeTypes.COMPOUND_EXPRESSION,
loc:child.loc,
children:[child]
}
}
currentContainer.children.push(` + `,next);
children.splice(j,1);
j--;
}else{
currentContainer = undefined;
break;
}
}
}
}
if (!hasText || children.length == 1) { // 一个元素不用管,可以执行innerHTML
return
}
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {
const callArgs = []
callArgs.push(child)
if (child.type !== NodeTypes.TEXT) { // 如果不是文本
callArgs.push(PatchFlags.TEXT + '')
}
// 全部格式话成文本调用
children[i] = {
type: NodeTypes.TEXT_CALL, // 最终需要变成createTextVnode() 增加patchFlag
content: child,
loc: child.loc,
codegenNode: createCallExpression(context,callArgs) // 创建表达式调用
}
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
runtimeHelpers.ts
export const CREATE_TEXT = Symbol(`createTextVNode`)
export const helperNameMap = {
[CREATE_TEXT]:`createTextVNode`
}
1
2
3
4
2
3
4
ast.ts
export function createCallExpression(context,args){
let callee = context.helper(CREATE_TEXT); // 生成代码时需要createTextVNode方法
return {
callee,
type: NodeTypes.JS_CALL_EXPRESSION,
arguments: args
}
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
转化元素
export function createObjectExpression(properties){
return {
type: NodeTypes.JS_OBJECT_EXPRESSION,
properties
}
}
export function transformElement(node,context){
if(node.type === NodeTypes.ELEMENT ){
return function postTransformElement(){ // 元素处理的退出函数
let vnodeTag = `'${node.tag}'`;
let properties = [];
let props = node.props
for(let i = 0 ; i< props.length; i++){ // 这里属性其实应该在codegen里在处理
properties.push({
key:props[i].name,
value:props[i].value.content
})
}
const propsExpression = props.length > 0 ? createObjectExpression(properties):null
let vnodeChildren = null;
if (node.children.length === 1) {
// 只有一个孩子节点 ,那么当生成 render 函数的时候就不用 [] 包裹
const child = node.children[0];
vnodeChildren = child;
}else{
if(node.children.length > 0){ // 处理儿子节点
vnodeChildren = node.children
}
}
// 代码生成
node.codegenNode = createVNodeCall(context,vnodeTag,propsExpression,vnodeChildren);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
runtimeHelpers.ts
export const CREATE_ELEMENT_VNODE = Symbol("createElementVNode");
export const helperNameMap = {
[CREATE_ELEMENT_VNODE]: "createElementVNode", // 创建元素节点标识
}
1
2
3
4
2
3
4
export function createVNodeCall(context,tag,props,children){
context.helper(CREATE_ELEMENT_VNODE);
return {
type:NodeTypes.VNODE_CALL,
tag,
props,
children
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
转化根节点
转化根节点需要添加block节点
export const FRAGMENT = Symbol("FRAGMENT");
export const CREATE_ELEMENT_BLOCK = Symbol(`createElementBlock`)
export const OPEN_BLOCK = Symbol(`openBlock`)
export const helperNameMap = {
[FRAGMENT]: "Fragment",
[OPEN_BLOCK]: `openBlock`, // block处理
[CREATE_ELEMENT_BLOCK]: `createElementBlock`
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
function createTransformContext(root){
const context = {
removeHelper(name){
const count = context.helpers.get(name);
if(count){
const currentCount = count - 1;
if(!currentCount){
context.helpers.delete(name);
}else{
context.helpers.set(name,currentCount)
}
}
},
// ...
}
return context
}
function createRootCodegen(root,context){
let {children } = root
if(children.length == 1){
const child = children[0];
if (child.type === NodeTypes.ELEMENT && child.codegenNode) {
const codegenNode = child.codegenNode;
root.codegenNode = codegenNode;
context.removeHelper(CREATE_ELEMENT_VNODE); // 不要创建元素
context.helper(OPEN_BLOCK)
context.helper(CREATE_ELEMENT_BLOCK); // 创建元素block就好了
root.codegenNode.isBlock = true; // 只有一个元素节点,那么他就是block节点
} else {
root.codegenNode = child; // 直接用里面的节点换掉
}
}else{
root.codegenNode = createVNodeCall(context,context.helper(FRAGMENT),undefined,root.children)
context.helper(OPEN_BLOCK)
context.helper(CREATE_ELEMENT_BLOCK)
root.codegenNode.isBlock = true; // 增加block fragment
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
export function transform(root){
// 创建转化的上下文, 记录转化方法及当前转化节点
let context = createTransformContext(root)
// 递归遍历
traverseNode(root,context)
createRootCodegen(root,context); // 生成根节点的codegen
root.helpers = [...context.helpers.keys()]
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
整个transform的流程,就是给ast节点添加codegenNode属性,用于方便生成对应的代码,并且收集生成代码所需的方法。