128 lines
3.6 KiB
JavaScript
128 lines
3.6 KiB
JavaScript
/* @flow */
|
|
|
|
import { makeMap, isBuiltInTag, cached, no } from 'shared/util'
|
|
|
|
let isStaticKey
|
|
let isPlatformReservedTag
|
|
|
|
const genStaticKeysCached = cached(genStaticKeys)
|
|
|
|
/**
|
|
* Goal of the optimizer: walk the generated template AST tree
|
|
* and detect sub-trees that are purely static, i.e. parts of
|
|
* the DOM that never needs to change.
|
|
*
|
|
* Once we detect these sub-trees, we can:
|
|
*
|
|
* 1. Hoist them into constants, so that we no longer need to
|
|
* create fresh nodes for them on each re-render;
|
|
* 2. Completely skip them in the patching process.
|
|
*/
|
|
export function optimize (root: ?ASTElement, options: CompilerOptions) {
|
|
if (!root) return
|
|
isStaticKey = genStaticKeysCached(options.staticKeys || '')
|
|
isPlatformReservedTag = options.isReservedTag || no
|
|
// first pass: mark all non-static nodes.
|
|
markStatic(root)
|
|
// second pass: mark static roots.
|
|
markStaticRoots(root, false)
|
|
}
|
|
|
|
function genStaticKeys (keys: string): Function {
|
|
return makeMap(
|
|
'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap' +
|
|
(keys ? ',' + keys : '')
|
|
)
|
|
}
|
|
|
|
function markStatic (node: ASTNode) {
|
|
node.static = isStatic(node)
|
|
if (node.type === 1) {
|
|
// do not make component slot content static. this avoids
|
|
// 1. components not able to mutate slot nodes
|
|
// 2. static slot content fails for hot-reloading
|
|
if (
|
|
!isPlatformReservedTag(node.tag) &&
|
|
node.tag !== 'slot' &&
|
|
node.attrsMap['inline-template'] == null
|
|
) {
|
|
return
|
|
}
|
|
for (let i = 0, l = node.children.length; i < l; i++) {
|
|
const child = node.children[i]
|
|
markStatic(child)
|
|
if (!child.static) {
|
|
node.static = false
|
|
}
|
|
}
|
|
if (node.ifConditions) {
|
|
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
|
|
const block = node.ifConditions[i].block
|
|
markStatic(block)
|
|
if (!block.static) {
|
|
node.static = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function markStaticRoots (node: ASTNode, isInFor: boolean) {
|
|
if (node.type === 1) {
|
|
if (node.static || node.once) {
|
|
node.staticInFor = isInFor
|
|
}
|
|
// For a node to qualify as a static root, it should have children that
|
|
// are not just static text. Otherwise the cost of hoisting out will
|
|
// outweigh the benefits and it's better off to just always render it fresh.
|
|
if (node.static && node.children.length && !(
|
|
node.children.length === 1 &&
|
|
node.children[0].type === 3
|
|
)) {
|
|
node.staticRoot = true
|
|
return
|
|
} else {
|
|
node.staticRoot = false
|
|
}
|
|
if (node.children) {
|
|
for (let i = 0, l = node.children.length; i < l; i++) {
|
|
markStaticRoots(node.children[i], isInFor || !!node.for)
|
|
}
|
|
}
|
|
if (node.ifConditions) {
|
|
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
|
|
markStaticRoots(node.ifConditions[i].block, isInFor)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function isStatic (node: ASTNode): boolean {
|
|
if (node.type === 2) { // expression
|
|
return false
|
|
}
|
|
if (node.type === 3) { // text
|
|
return true
|
|
}
|
|
return !!(node.pre || (
|
|
!node.hasBindings && // no dynamic bindings
|
|
!node.if && !node.for && // not v-if or v-for or v-else
|
|
!isBuiltInTag(node.tag) && // not a built-in
|
|
isPlatformReservedTag(node.tag) && // not a component
|
|
!isDirectChildOfTemplateFor(node) &&
|
|
Object.keys(node).every(isStaticKey)
|
|
))
|
|
}
|
|
|
|
function isDirectChildOfTemplateFor (node: ASTElement): boolean {
|
|
while (node.parent) {
|
|
node = node.parent
|
|
if (node.tag !== 'template') {
|
|
return false
|
|
}
|
|
if (node.for) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|