UNPKG

4.23 kBJavaScriptView Raw
1/* @flow */
2
3/**
4 * In SSR, the vdom tree is generated only once and never patched, so
5 * we can optimize most element / trees into plain string render functions.
6 * The SSR optimizer walks the AST tree to detect optimizable elements and trees.
7 *
8 * The criteria for SSR optimizability is quite a bit looser than static tree
9 * detection (which is designed for client re-render). In SSR we bail only for
10 * components/slots/custom directives.
11 */
12
13import { no, makeMap, isBuiltInTag } from 'shared/util'
14
15// optimizability constants
16export const optimizability = {
17 FALSE: 0, // whole sub tree un-optimizable
18 FULL: 1, // whole sub tree optimizable
19 SELF: 2, // self optimizable but has some un-optimizable children
20 CHILDREN: 3, // self un-optimizable but have fully optimizable children
21 PARTIAL: 4 // self un-optimizable with some un-optimizable children
22}
23
24let isPlatformReservedTag
25
26export function optimize (root: ?ASTElement, options: CompilerOptions) {
27 if (!root) return
28 isPlatformReservedTag = options.isReservedTag || no
29 walk(root, true)
30}
31
32function walk (node: ASTNode, isRoot?: boolean) {
33 if (isUnOptimizableTree(node)) {
34 node.ssrOptimizability = optimizability.FALSE
35 return
36 }
37 // root node or nodes with custom directives should always be a VNode
38 const selfUnoptimizable = isRoot || hasCustomDirective(node)
39 const check = child => {
40 if (child.ssrOptimizability !== optimizability.FULL) {
41 node.ssrOptimizability = selfUnoptimizable
42 ? optimizability.PARTIAL
43 : optimizability.SELF
44 }
45 }
46 if (selfUnoptimizable) {
47 node.ssrOptimizability = optimizability.CHILDREN
48 }
49 if (node.type === 1) {
50 for (let i = 0, l = node.children.length; i < l; i++) {
51 const child = node.children[i]
52 walk(child)
53 check(child)
54 }
55 if (node.ifConditions) {
56 for (let i = 1, l = node.ifConditions.length; i < l; i++) {
57 const block = node.ifConditions[i].block
58 walk(block, isRoot)
59 check(block)
60 }
61 }
62 if (node.ssrOptimizability == null ||
63 (!isRoot && (node.attrsMap['v-html'] || node.attrsMap['v-text']))
64 ) {
65 node.ssrOptimizability = optimizability.FULL
66 } else {
67 node.children = optimizeSiblings(node)
68 }
69 } else {
70 node.ssrOptimizability = optimizability.FULL
71 }
72}
73
74function optimizeSiblings (el) {
75 const children = el.children
76 const optimizedChildren = []
77
78 let currentOptimizableGroup = []
79 const pushGroup = () => {
80 if (currentOptimizableGroup.length) {
81 optimizedChildren.push({
82 type: 1,
83 parent: el,
84 tag: 'template',
85 attrsList: [],
86 attrsMap: {},
87 rawAttrsMap: {},
88 children: currentOptimizableGroup,
89 ssrOptimizability: optimizability.FULL
90 })
91 }
92 currentOptimizableGroup = []
93 }
94
95 for (let i = 0; i < children.length; i++) {
96 const c = children[i]
97 if (c.ssrOptimizability === optimizability.FULL) {
98 currentOptimizableGroup.push(c)
99 } else {
100 // wrap fully-optimizable adjacent siblings inside a template tag
101 // so that they can be optimized into a single ssrNode by codegen
102 pushGroup()
103 optimizedChildren.push(c)
104 }
105 }
106 pushGroup()
107 return optimizedChildren
108}
109
110function isUnOptimizableTree (node: ASTNode): boolean {
111 if (node.type === 2 || node.type === 3) { // text or expression
112 return false
113 }
114 return (
115 isBuiltInTag(node.tag) || // built-in (slot, component)
116 !isPlatformReservedTag(node.tag) || // custom component
117 !!node.component || // "is" component
118 isSelectWithModel(node) // <select v-model> requires runtime inspection
119 )
120}
121
122const isBuiltInDir = makeMap('text,html,show,on,bind,model,pre,cloak,once')
123
124function hasCustomDirective (node: ASTNode): ?boolean {
125 return (
126 node.type === 1 &&
127 node.directives &&
128 node.directives.some(d => !isBuiltInDir(d.name))
129 )
130}
131
132// <select v-model> cannot be optimized because it requires a runtime check
133// to determine proper selected option
134function isSelectWithModel (node: ASTNode): boolean {
135 return (
136 node.type === 1 &&
137 node.tag === 'select' &&
138 node.directives != null &&
139 node.directives.some(d => d.name === 'model')
140 )
141}