Vue.js模板编译器用于把模板编译为渲染函数:
- 分析模板,将其解析为AST
- 将模板AST转换为用于描述渲染函数的JavaScript AST
- 根据JavaScript AST生成渲染函数代码
解析Token
为Vue.js模板构造AST,AST在结构上和模板同构
首先,将模板解析为一个个token,利用有限状态机进行分词
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| const State = { initial: 1, tagOpen: 2, tagName: 3, text: 4, tagEnd: 5, tagEndName: 6, };
function isAlpha(char) { return (char >= "a" && char <= "z") || (char >= "A" && char <= "Z"); }
function tokenize(str) { let currentState = State.initial; const chars = []; const tokens = []; while (str) { const char = str[0]; switch (currentState) { case State.initial: if (char === "<") { currentState = State.tagOpen; str = str.slice(1); } else if (isAlpha(str)) { currentState = State.text; chars.push(char); str = str.slice(1); } break; case State.tagOpen: if (isAlpha(char)) { currentState = State.tagName; chars.push(char); str = str.slice(1); } else if (char === "/") { currentState = State.tagEnd; str = str.slice(1); } break; case State.tagName: if (isAlpha(char)) { chars.push(char); str = str.slice(1); } else if (char === ">") { currentState = State.initial; tokens.push({ type: "tag", name: chars.join(""), }); chars.length = 0; str = str.slice(1); } break; case State.text: if (isAlpha(char)) { chars.push(char); str = str.slice(1); } else if (char === "<") { currentState = State.tagOpen; tokens.push({ type: "text", content: chars.join(""), }); chars.length = 0; str = str.slice(1); } break; case State.tagEnd: if (isAlpha(char)) { currentState = State.tagEndName; chars.push(char); str = str.slice(1); } break; case State.tagEndName: if (isAlpha(char)) { chars.push(char); str = str.slice(1); } else if (char === ">") { currentState = State.initial; tokens.push({ type: "tagEnd", name: chars.join(""), }); } chars.length = 0; str = str.slice(1); break; } } return tokens; }
|
比如:
1
| const tokens = tokenize(`<div><p>Vue</p></div>)
|
得到:
1 2 3 4 5 6 7 8
| const tokens = [ {type:"tag",name:"div"}, {type:'tag',name:'p'}, {type:'text',context:'Vue'}, {type:'tagEnd',name:'p'}, {type:'tagEnd',name:'div'} ]
|
构建AST:
接下来,扫描token列表构建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
|
function parse(str) { const tokens = tokenize(str) const root = { type: 'Root', children: [] } const elementStack = [root] while(tokens.length) { const parent = elementStack[elementStack.length-1] const t = tokens[0] switch(t.type){ case 'tag': const elementNode = { type: 'Element', tag: t.name, children: [] } parent.children.push(elementNode) elementStack.push(elementNode) break; case 'text': const textNode = { type: 'Text', content: t.content } parent.children.push(textNode) break; case 'tagEnd': elementStack.pop() break; } tokens.shift()
} }
|
AST的转换
AST的转换,即对AST的一系列操作
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 57 58 59 60 61 62 63 64 65 66 67
|
function traverseNode(ast,context) { context.currentNode = ast const transforms = context.nodeTransforms for(let i = 0;i<transforms.length;i++){ transforms[i](context.currentNode,context) } const children = context.currentNode.children for(let i = 0;i<children.length;i++){ context.parent = context.currentNode context.childIndex = i traverseNode(children[i],context) } } function transformElement(node,context){ if(node.type === 'Element' && node.tag === 'p') { context.removeNode() } } function transformText(node,context) { if(node.type === 'Text') { context.replaceNode({ type:'Element', tag: 'span' }) } }
function transform(ast) { const context = { currentNode:null, childIndex: 0, parent: null, replaceNode(node){ context.parent.children[context.childIndex]=node context.currentNode = node }, removeNode(node){ context.parent.children.splice(context.childIndex,1) context.currentNode = null }, nodeTransforms: [ transformElement, transformText ] } traverseNode(ast,context) }
|