Vue3
中编译原理(二)
从零手写一.Vue3 transform实现
定义转化标识
export const CREATE_VNODE = Symbol('createVnode');
export const TO_DISPALY_STRING = Symbol('toDisplayString');
export const OPEN_BLOCK = Symbol('openBlock');
export const CREATE_BLOCK = Symbol('createBlock')
export const FRAGMENT = Symbol('Fragment');
export const CREATE_TEXT = Symbol('createTextVNode');
export const helperNameMap: any = {
[FRAGMENT]: `Fragment`,
[OPEN_BLOCK]: `openBlock`,
[CREATE_BLOCK]: `createBlock`,
[CREATE_VNODE]: `createVNode`,
[TO_DISPALY_STRING]: "toDisplayString",
[CREATE_TEXT]: "createTextVNode"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
对AST语法树进行转化,主要是对AST语法树进行优化操作
export function baseCompile(template) {
// 1.生成ast语法树
const ast = baseParse(template);
// 得到对应的转化方法 元素转化、文本转化... 还有指令转化都应该在这里实现
const nodeTransforms = getBaseTransformPreset();
transform(ast, { nodeTransform })
}
1
2
3
4
5
6
7
2
3
4
5
6
7
function transformElement(node,context) {
// 转化标签 需要处理他的子节点,所以需要等待子节点遍历完成在处理
if(!(node.type === 1)){ // 不是元素就不必执行了
return;
}
console.log('转化元素',node)
}
function transformText(node,context) {
// 处理文本 需要处理他的同级 表达式/文本,所以需要处理完同级后在处理
if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ElEMENT) {
console.log('内部可能包含文本', node);
}
}
function getBaseTransformPreset() {
return [
transformElement,
transformText
]// ...指令转化
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
开始进行转化,会先创建转化上下文,之后遍历ast树
function createTransformContext(root, { nodeTransforms }) {
const context = {
root, // 转化的完整ast
currentNode: root, // 当前转化的节点
nodeTransforms, // 转化方法
helpers: new Set(), // 收集导入的包
helper(name) {
context.helpers.add(name);
return name;
}
}
return context;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
function transform(root, options) {
const context = createTransformContext(root, options);
traverseNode(root, context)
}
1
2
3
4
2
3
4
深度遍历节点,调用transform函数
function traverseNode(node, context) { // 遍历树
context.currentNode = node;
const { nodeTransforms } = context;
for (let i = 0; i < nodeTransforms.length; i++) {
nodeTransforms[i](node, context);
}
switch (node.type) {
case NodeTypes.ROOT:
case NodeTypes.ElEMENT:
traverseChildren(node, context); // 递归遍历子节点
break;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
function traverseChildren(parent, context) {
for (let i = 0; i < parent.children.length; i++) {
const child = parent.children[i];
traverseNode(child, context); // 遍历节点
}
}
1
2
3
4
5
6
2
3
4
5
6
1.退出函数
返回一个函数等递归完成后在执行
function transformElement(node,context) {
// 转化标签 需要处理他的子节点,所以需要等待子节点遍历完成在处理
if(!(node.type === 1)){ // 不是元素就不必执行了
return;
}
return ()=>{
console.log('转化元素',node)
}
}
function transformText(node,context) {
// 处理文本 需要处理他的同级 表达式/文本,所以需要处理完同级后在处理
if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ElEMENT) {
return ()=>{
console.log('内部可能包含文本', node);
}
}
}
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
function traverseNode(node, context) { // 遍历树
context.currentNode = node;
const { nodeTransforms } = context;
const exitFns = [];
for (let i = 0; i < nodeTransforms.length; i++) {
const onExit = nodeTransforms[i](node, context);
if (onExit) exitFns.push(onExit)
}
switch (node.type) {
case NodeTypes.ROOT:
case NodeTypes.ElEMENT:
traverseChildren(node, context);
break;
}
let i = exitFns.length;
context.currentNode = node; // 保证退出方法的context是正确的
while (i--) {
exitFns[i]()
}
}
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
2.文本转化
export const enum PatchFlags {
TEXT = 1,
CLASS = 1 << 1,
STYLE = 1 << 2,
PROPS = 1 << 3,
FULL_PROPS = 1 << 4,
HYDRATE_EVENTS = 1 << 5,
STABLE_FRAGMENT = 1 << 6,
KEYED_FRAGMENT = 1 << 7,
UNKEYED_FRAGMENT = 1 << 8,
NEED_PATCH = 1 << 9,
DYNAMIC_SLOTS = 1 << 10,
DEV_ROOT_FRAGMENT = 1 << 11,
HOISTED = -1,
BAIL = -2
}
function isText(node) {
return node.type == NodeTypes.INTERPOLATION || node.type == NodeTypes.TEXT;
}
export function createCallExpression(callee,args) {
return {
type: NodeTypes.JS_CALL_EXPRESSION,
callee,
arguments: args
}
}
function transformText(node, context) { // 转化文本 核心就是相邻的合并
if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ElEMENT) {
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,
content: child,
loc: child.loc,
codegenNode: createCallExpression(
context.helper(CREATE_TEXT),
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
switch (node.type) {
case NodeTypes.ROOT:
case NodeTypes.ElEMENT:
traverseChildren(node, context);
case NodeTypes.INTERPOLATION: // 给表达式新增导入方法
context.helper(TO_DISPALY_STRING);
break;
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
3.元素转化
function createVNodeCall(context, tag, props, children, patchFlag) {
context.helper(CREATE_VNODE);
return {
type: NodeTypes.VNODE_CALL,
tag,
props,
children,
patchFlag
}
}
function transformElement(node, context) {
if (!(node.type === 1)) {
return;
}
return () => { // 对元素的处理
const { tag, children } = node;
const vnodeTag = `"${tag}"`; // 标签名
let vnodeProps;
let vnodeChildren;
let vnodePatchFlag;
let patchFlag = 0;
if (node.children.length > 0) {
if (node.children.length === 1) {
const child = node.children[0];
const type = child.type;
const hasDynamicTextChild = type === NodeTypes.INTERPOLATION || type == NodeTypes.COMPOUND_EXPRESSION;
if (hasDynamicTextChild) {
patchFlag |= PatchFlags.TEXT;
}
vnodeChildren = child; // 一个儿子去掉数组
} else {
vnodeChildren = children;
}
}
if (patchFlag !== 0) {
vnodePatchFlag = String(patchFlag);
}
node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren, vnodePatchFlag);
}
}
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
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
4.根元素的转化
function createRootCodegen(root, context) {
const { helper } = context
const { children } = root
if (children.length == 1) {
const child = children[0];
// 就一个元素
const codegenNode = child.codegenNode;
codegenNode.isBlock = true
helper(OPEN_BLOCK)
helper(CREATE_BLOCK);
root.codegenNode = codegenNode; // 单个节点就转化成一个block
} else if (children.length > 1) { // 增加fragment
root.codegenNode = createVNodeCall(
context,
helper(FRAGMENT),
undefined,
root.children,
PatchFlags.STABLE_FRAGMENT
)
}
}
export function transform(root, options) {
const context = createTransformContext(root, options);
traverseNode(root, context);
createRootCodegen(root, context)
root.helpers = [...context.helpers]
}
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
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