optimizer.js
4.13 KB
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
129
130
131
132
133
134
135
136
137
138
139
140
141
/* @flow */
/**
* In SSR, the vdom tree is generated only once and never patched, so
* we can optimize most element / trees into plain string render functions.
* The SSR optimizer walks the AST tree to detect optimizable elements and trees.
*
* The criteria for SSR optimizability is quite a bit looser than static tree
* detection (which is designed for client re-render). In SSR we bail only for
* components/slots/custom directives.
*/
import { no, makeMap, isBuiltInTag } from 'shared/util'
// optimizability constants
export const optimizability = {
FALSE: 0, // whole sub tree un-optimizable
FULL: 1, // whole sub tree optimizable
SELF: 2, // self optimizable but has some un-optimizable children
CHILDREN: 3, // self un-optimizable but have fully optimizable children
PARTIAL: 4 // self un-optimizable with some un-optimizable children
}
let isPlatformReservedTag
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
isPlatformReservedTag = options.isReservedTag || no
walk(root, true)
}
function walk (node: ASTNode, isRoot?: boolean) {
if (isUnOptimizableTree(node)) {
node.ssrOptimizability = optimizability.FALSE
return
}
// root node or nodes with custom directives should always be a VNode
const selfUnoptimizable = isRoot || hasCustomDirective(node)
const check = child => {
if (child.ssrOptimizability !== optimizability.FULL) {
node.ssrOptimizability = selfUnoptimizable
? optimizability.PARTIAL
: optimizability.SELF
}
}
if (selfUnoptimizable) {
node.ssrOptimizability = optimizability.CHILDREN
}
if (node.type === 1) {
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
walk(child)
check(child)
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block
walk(block, isRoot)
check(block)
}
}
if (node.ssrOptimizability == null ||
(!isRoot && (node.attrsMap['v-html'] || node.attrsMap['v-text']))
) {
node.ssrOptimizability = optimizability.FULL
} else {
node.children = optimizeSiblings(node)
}
} else {
node.ssrOptimizability = optimizability.FULL
}
}
function optimizeSiblings (el) {
const children = el.children
const optimizedChildren = []
let currentOptimizableGroup = []
const pushGroup = () => {
if (currentOptimizableGroup.length) {
optimizedChildren.push({
type: 1,
parent: el,
tag: 'template',
attrsList: [],
attrsMap: {},
rawAttrsMap: {},
children: currentOptimizableGroup,
ssrOptimizability: optimizability.FULL
})
}
currentOptimizableGroup = []
}
for (let i = 0; i < children.length; i++) {
const c = children[i]
if (c.ssrOptimizability === optimizability.FULL) {
currentOptimizableGroup.push(c)
} else {
// wrap fully-optimizable adjacent siblings inside a template tag
// so that they can be optimized into a single ssrNode by codegen
pushGroup()
optimizedChildren.push(c)
}
}
pushGroup()
return optimizedChildren
}
function isUnOptimizableTree (node: ASTNode): boolean {
if (node.type === 2 || node.type === 3) { // text or expression
return false
}
return (
isBuiltInTag(node.tag) || // built-in (slot, component)
!isPlatformReservedTag(node.tag) || // custom component
!!node.component || // "is" component
isSelectWithModel(node) // <select v-model> requires runtime inspection
)
}
const isBuiltInDir = makeMap('text,html,show,on,bind,model,pre,cloak,once')
function hasCustomDirective (node: ASTNode): ?boolean {
return (
node.type === 1 &&
node.directives &&
node.directives.some(d => !isBuiltInDir(d.name))
)
}
// <select v-model> cannot be optimized because it requires a runtime check
// to determine proper selected option
function isSelectWithModel (node: ASTNode): boolean {
return (
node.type === 1 &&
node.tag === 'select' &&
node.directives != null &&
node.directives.some(d => d.name === 'model')
)
}