Vue2.x源码分析 - 解析Template模板
# # Vue2.x源码分析 - 解析Template模板
编译过程首先就是对模板做解析,生成 AST,它是一种抽象语法树,是对源代码的抽象语法结构的树状表现形式。
Vue的实现方式是从头开始解析,运用正则表达式查找,逐部分字符分析、处理、删减,直到处理完整个template字符,最终得到一颗完整的AST Tree。代码在src/compiler/parser/index.js
中的parseHTML()
。
# # 整体流程
遇到注释/doctype时,游标跳过整段。
遇到开始标签<
- 解析tag标签,拿到startTagMatch
- 读取tag标签, 如'<h1',游标向前'<tag'
- 读取标签上属性。游标向前移动'所有属性'
- /^\s*([^\s"'<>/=]+)(?:\s*(=)\s*(?:"([^"])"+|'([^'])'+|([^\s"'=<>`]+)))?/
- 读取tag标签的右尖括号'>',游标向前加1
- /^\s*(/?)>/
- 处理tag和属性(执行option.start),管理ASTElement。(创建 AST 元素,处理 AST 元素,AST 树管理(比如维护父子关系,这时候就用到stack栈)。)
if (inVPre) {
processRawAttrs(element)
} else if (!element.processed) {
// structural directives
processFor(element)
processIf(element)
processOnce(element)
processElement(element, options) // processKey/processRef/processSlot/processComponent/processAttrs
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
3. 使用stack压入tag标签,通过这个栈可得知标签的父子关系 ![image](/assets/images-1/44.png)
- 遇到纯文本,解析,游标往前移动。执行options.chars
- 遇到结束标签</>
- 执行option.end,游标向前移动
export function parseHTML (html, options) {
let lastTag
while (html) {
if (!lastTag || !isPlainTextElement(lastTag)){
let textEnd = html.indexOf('<')
// 如果是<开头
if (textEnd === 0) {
// 注释/Doctype都是匹配整个区域
if(matchComment) {
advance(commentLength)
continue
}
if(matchDoctype) {
advance(doctypeLength)
continue
}
// 匹配结尾,如</h1>。new RegExp(`^<\\/${qnameCapture}[^>]*>`)
if(matchEndTag) {
advance(endTagLength)
parseEndTag() // 1. stack弹出标签 2. options.end
continue
}
// 匹配开始
if(matchStartTag) {
parseStartTag() // 将<h1 name="123">分别处理为:<h1、name="123"、>
handleStartTag() // stack压入 2. options.start
continue
}
}
// 开闭标签之间的纯文本
handleText()
advance(textLength)
} else {
// 处理特殊的script,style,textarea
handlePlainTextElement()
parseEndTag()
}
}
}
function advance (n) {
index += n
html = html.substring(n) // 每次都截取文字
}
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
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
// 处理时html在变化(游标作用)
" v-for="branch in branches">
<input type="radio" :id="branch" :value="branch" name="branch" v-model="currentBranch">
<label :for="branch">{{ branch }}</label>
</template>
<p>vuejs/vue@{{ currentBranch }}</p>
</div>"
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# # AST Tree
<ul :class="bindCls" class="list" v-if="isShow">
<li v-for="(item,index) in data" @click="clickItem(index)">{{item}}:{{index}}</li>
</ul>
1
2
3
4
2
3
4
ast = {
'type': 1,
'tag': 'ul',
'attrsList': [],
'attrsMap': {
':class': 'bindCls',
'class': 'list',
'v-if': 'isShow'
},
'if': 'isShow',
'ifConditions': [{
'exp': 'isShow',
'block': // ul ast element
}],
'parent': undefined,
'plain': false,
'staticClass': 'list',
'classBinding': 'bindCls',
'children': [{
'type': 1,
'tag': 'li',
'attrsList': [{
'name': '@click',
'value': 'clickItem(index)'
}],
'attrsMap': {
'@click': 'clickItem(index)',
'v-for': '(item,index) in data'
},
'parent': // ul ast element
'plain': false,
'events': {
'click': {
'value': 'clickItem(index)'
}
},
'hasBindings': true,
'for': 'data',
'alias': 'item',
'iterator1': 'index',
'children': [
'type': 2,
'expression': '_s(item)+":"+_s(index)'
'text': '{{item}}:{{index}}',
'tokens': [
{'@binding':'item'},
':',
{'@binding':'index'}
]
]
}]
}
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
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
编辑 (opens new window)