# WebRuntime 情况下涉及模板编译部分
- Vue 更新是为了生成 render 函数,然后生成虚拟 dom,映射到页面上。
- render 方法比 template更加底层,如果在创建时传入了 render 函数,模板编译这一步会跳过。比如 weex 与 web 就不需要执行模板编译部分
- compiler 分为三个阶段:生成AST、优化静态内容、生成render
- 创建 compiler部分,分为 baseCompile 与 createCompiler 结合实现
- 返回 compile 方法?
function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options) // 模板解析
if (options.optimize !== false) {
optimize(ast, options) // 标记是否为静态节点
}
const code = generate(ast, options) // 生成render
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
}
// createCompiler
function createCompiler (baseOptions: CompilerOptions) {
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
// errors, tips处理
// 更新options参数处理: warn,
// modules, directives, tiptions 的合并,此处省略
// 开始编译
const compiled = baseCompile(template.trim(), finalOptions)
if (process.env.NODE_ENV !== 'production') {
detectErrors(compiled.ast, warn)
}
compiled.errors = errors
compiled.tips = tips
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
function createFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err, code })
return noop
}
}
function createCompileToFunctionFn (compile: Function): Function {
const cache = Object.create(null)
return function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
// 参数处理与警告处理,省略
// 根据整个模板缓存,检查是否已经缓存了
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
// 执行 compile
const compiled = compile(template, options)
// 检查复杂的 errors/tips,省略
// turn code into functions
const res = {}
const fnGenErrors = []
res.render = createFunction(compiled.render, fnGenErrors)
res.staticRenderFns = compiled.staticRenderFns.map(code => {
return createFunction(code, fnGenErrors)
})
// 检查生成函数的 errors,省略
return (cache[key] = res)
}
}
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
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
生成的结果如下
render = function () {
with(this){return _c('div',{attrs:{"id":"app"}},[_c('p',[_v(_s(message))])])}
}
staticRenderFns = function () {
with(this){return _c('p',[_v("这是"),_c('span',[_v("静态内容")])])}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 模板解析
- 解析 HTML,parse为主线函数,包含parseHTML,再对具体的进行 ParseText 与 ParseText
function parse (
template: string,
options: CompilerOptions
): ASTElement | void {
// ...
parseHTML(template, {
// 其他options,
start (tag, attrs, unary, start, end) {
// 检查 namespace
// handle IE svg bug
// 创建 AST 节点
let element: ASTElement = createASTElement(tag, attrs, currentParent)
if (ns) {
element.ns = ns
}
// AST 节点属性处理
// 节点检查
// apply pre-transforms
// 处理各种指令
// inPre, v-pre相当于avalon的skip,即在它控制的作用域下不编译指令或者表达式 或 预留的</pre> 标签
// processPre
// processRawAttrs
// processFor(element)
// processIf(element)
// processOnce(element)
// 是否设置为 root
// 未闭合的标签,更新为 currentParent,并进栈
if (!unary) {
currentParent = element
stack.push(element)
} else {
// 闭合标签
closeElement(element)
}
},
end (tag, start, end) {
// 取出栈顶元素,进行标签闭合
const element = stack[stack.length - 1]
// pop stack
stack.length -= 1
currentParent = stack[stack.length - 1]
closeElement(element)
},
chars (text: string, start: number, end: number) {
// warn ...
if (!currentParent) {
return
}
const children = currentParent.children
// text,如果是非style, script则进行 decode 及预留空白符处理
if (inPre || text.trim()) {
text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
} else if (!children.length) {
text = ''
} else if (whitespaceOption) {
if (whitespaceOption === 'condense') {
text = lineBreakRE.test(text) ? '' : ' '
} else {
text = ' '
}
} else {
text = preserveWhitespace ? ' ' : ''
}
if (text) {
if (!inPre && whitespaceOption === 'condense') {
// whitespaceRE = /[ \f\t\r\n]+/g 空白符统一处理
text = text.replace(whitespaceRE, ' ')
}
let res
let child: ?ASTNode
// 进行文本解析
if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
child = {
type: 2,
expression: res.expression,
tokens: res.tokens,
text
}
// 静态文本
} else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
child = {
type: 3,
text
}
}
if (child) {
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
child.start = start
child.end = end
}
// 更新文本节点的信息
children.push(child)
}
}
},
comment (text: string, start, end) {
// 创建注释节点
if (currentParent) {
const child: ASTText = {
type: 3,
text,
isComment: true
}
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
child.start = start
child.end = end
}
currentParent.children.push(child)
}
}
})
return root
}
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
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
# 解析 HTML
- stack: 存放非自闭合标签
- isUnaryTag: 是否非自闭合标签
- index: expectHTML 检测到的当前游标的位置
- html:待检查的 html 部分
- last: 当前被检测的 html 部分的剩余部分
- rest: 暂存剩余包含注释或标签的未处理的 html
- lastTag: 上一个未闭合标签
- advance:负责移动游标
- textEnd:< 开始的位置
- 处理 HTML
- 没有未闭合标签 或 未闭合标签不是plaintext(如:script/style),textEnd = 0时,分为 5 种情况:
- 是注释标签,创建注释节点并移动游标
- 条件注释,移动游标
- doctype标签,移动游标
- 标签结束:new RegExp(
^<\\/${qnameCapture}[^>]*>) - 标签开始:完成匹配标签名、属性、开始与结束位置、是否是自闭合标签并更新游标;标签开始:new RegExp(
^<${qnameCapture}),与开始标签的结束 /^\s*(/?)>/- 处理属性: /^\s([^\s"'<>/=]+)(?:\s(=)\s*(?:"([^"])"+|'([^'])'+|([^\s"'=<>`]+)))?/**
- 动态属性: /^\s((?:v-[\w-]+:|@|:|#)[[^=]+?][^\s"'<>/=])(?:\s(=)\s(?:"([^"])"+|'([^'])'+|([^\s"'=<>`]+)))?/**
- 标签命名规则: ncname =
[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp.source}]* - 标签名匹配:qnameCapture =
((?:${ncname}\\:)?${ncname})
- 更新 textEnd 并取出对应的文本,更新文本子节点
- 处理文本子节点
- 没有未闭合标签 或 未闭合标签不是plaintext(如:script/style),textEnd = 0时,分为 5 种情况:
- 处理所有还未闭合标签的逻辑
function parseHTML (html, options) {
const stack = []
const expectHTML = options.expectHTML
const isUnaryTag = options.isUnaryTag || no
const canBeLeftOpenTag = options.canBeLeftOpenTag || no // 像colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source这些节点,不会直接包裹同类型的节点,包裹即错误的,会进行修正处理
let index = 0
let last, lastTag
while (html) {
last = html
if (!lastTag || !isPlainTextElement(lastTag)) {
let textEnd = html.indexOf('<')
if (textEnd === 0) {
// 注释标签
if (comment.test(html)) { // /^<!\--/
const commentEnd = html.indexOf('-->')
if (commentEnd >= 0) {
if (options.shouldKeepComment) {
options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3)
}
advance(commentEnd + 3)
continue
}
}
if (conditionalComment.test(html)) { // /^<!\[/
const conditionalEnd = html.indexOf(']>')
if (conditionalEnd >= 0) {
advance(conditionalEnd + 2)
continue
}
}
const doctypeMatch = html.match(doctype) // /^<!DOCTYPE [^>]+>/i
if (doctypeMatch) {
advance(doctypeMatch[0].length)
continue
}
const endTagMatch = html.match(endTag)
if (endTagMatch) {
const curIndex = index
advance(endTagMatch[0].length)
parseEndTag(endTagMatch[1], curIndex, index)
continue
}
// Start tag:返回{tagName, unarySlash, attrs, start, end}
const startTagMatch = parseStartTag() // 完成匹配标签名、属性、开始与结束位置、是否是自闭合标签
if (startTagMatch) {
// 处理元素属性,并创建 VNODE 节点
handleStartTag(startTagMatch)
if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
advance(1)
}
continue
}
}
// 包含 <,不是结束标签、开始标签,也不是注释;next 指向下一个 < 的位置,更新textEnd的位置
let text, rest, next
if (textEnd >= 0) {
rest = html.slice(textEnd)
while (
!endTag.test(rest) &&
!startTagOpen.test(rest) &&
!comment.test(rest) &&
!conditionalComment.test(rest)
) {
next = rest.indexOf('<', 1)
if (next < 0) break
textEnd += next
rest = html.slice(textEnd)
}
text = html.substring(0, textEnd)
}
if (textEnd < 0) {
text = html
}
if (text) {
advance(text.length)
}
// 创建文本子节点
if (options.chars && text) {
options.chars(text, index - text.length, index)
}
} else {
let endTagLength = 0
const stackedTag = lastTag.toLowerCase()
// 获取 xxxxxx </stackedTag> 中的 text
const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))
const rest = html.replace(reStackedTag, function (all, text, endTag) {
endTagLength = endTag.length
// 获取其中的条件注释
if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
text = text
.replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298
.replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1')
}
// 去掉textarea, pre的第一个\n
if (shouldIgnoreFirstNewline(stackedTag, text)) {
text = text.slice(1)
}
// 创建文本子节点
if (options.chars) {
options.chars(text)
}
return ''
})
// 更新 游标位置,更新下次需要检测的部分
index += html.length - rest.length
html = rest
// 对具名tagName进行闭合
parseEndTag(stackedTag, index - endTagLength, index)
}
if (html === last) {
options.chars && options.chars(html)
break
}
}
// 对 stack 所有未闭合 tagName 进行元素闭合,并更新stack.length及lastTag
parseEndTag()
}
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
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