/* @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')
|
)
|
}
|