2019-10-04 11:30:25 +08:00
|
|
|
import {
|
|
|
|
RootNode,
|
|
|
|
NodeTypes,
|
|
|
|
TemplateChildNode,
|
2019-10-15 23:41:24 +08:00
|
|
|
SimpleExpressionNode,
|
2019-10-06 10:47:20 +08:00
|
|
|
ElementTypes,
|
2019-10-08 05:12:22 +08:00
|
|
|
PlainElementNode,
|
|
|
|
ComponentNode,
|
2019-10-10 23:19:17 +08:00
|
|
|
TemplateNode,
|
2019-10-19 09:51:34 +08:00
|
|
|
ElementNode,
|
2019-10-24 05:57:40 +08:00
|
|
|
PlainElementCodegenNode,
|
|
|
|
CodegenNodeWithDirective
|
2019-10-04 11:30:25 +08:00
|
|
|
} from '../ast'
|
|
|
|
import { TransformContext } from '../transform'
|
2019-10-19 04:35:01 +08:00
|
|
|
import { WITH_DIRECTIVES } from '../runtimeHelpers'
|
2019-10-15 23:41:24 +08:00
|
|
|
import { PatchFlags, isString, isSymbol } from '@vue/shared'
|
2019-10-10 23:19:17 +08:00
|
|
|
import { isSlotOutlet, findProp } from '../utils'
|
|
|
|
|
2019-10-04 21:03:00 +08:00
|
|
|
export function hoistStatic(root: RootNode, context: TransformContext) {
|
2019-10-08 05:12:22 +08:00
|
|
|
walk(
|
|
|
|
root.children,
|
|
|
|
context,
|
|
|
|
new Map(),
|
|
|
|
isSingleElementRoot(root, root.children[0])
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isSingleElementRoot(
|
|
|
|
root: RootNode,
|
|
|
|
child: TemplateChildNode
|
|
|
|
): child is PlainElementNode | ComponentNode | TemplateNode {
|
|
|
|
const { children } = root
|
|
|
|
return (
|
|
|
|
children.length === 1 &&
|
|
|
|
child.type === NodeTypes.ELEMENT &&
|
|
|
|
!isSlotOutlet(child)
|
|
|
|
)
|
2019-10-04 11:30:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
function walk(
|
|
|
|
children: TemplateChildNode[],
|
|
|
|
context: TransformContext,
|
2019-10-08 05:12:22 +08:00
|
|
|
resultCache: Map<TemplateChildNode, boolean>,
|
|
|
|
doNotHoistNode: boolean = false
|
2019-10-04 11:30:25 +08:00
|
|
|
) {
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
|
const child = children[i]
|
2019-10-05 02:34:26 +08:00
|
|
|
// only plain elements are eligible for hoisting.
|
|
|
|
if (
|
|
|
|
child.type === NodeTypes.ELEMENT &&
|
|
|
|
child.tagType === ElementTypes.ELEMENT
|
|
|
|
) {
|
2019-10-21 05:00:11 +08:00
|
|
|
if (!doNotHoistNode && isStaticNode(child, resultCache)) {
|
2019-10-04 11:30:25 +08:00
|
|
|
// whole tree is static
|
2019-10-08 22:50:00 +08:00
|
|
|
child.codegenNode = context.hoist(child.codegenNode!)
|
2019-10-04 11:30:25 +08:00
|
|
|
continue
|
2019-10-05 02:34:26 +08:00
|
|
|
} else {
|
|
|
|
// node may contain dynamic children, but its props may be eligible for
|
|
|
|
// hoisting.
|
2019-10-24 05:57:40 +08:00
|
|
|
const codegenNode = child.codegenNode!
|
|
|
|
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
|
|
|
|
const flag = getPatchFlag(codegenNode)
|
|
|
|
if (
|
|
|
|
(!flag ||
|
|
|
|
flag === PatchFlags.NEED_PATCH ||
|
|
|
|
flag === PatchFlags.TEXT) &&
|
|
|
|
!hasDynamicKeyOrRef(child) &&
|
|
|
|
!hasCachedProps(child)
|
|
|
|
) {
|
|
|
|
const props = getNodeProps(child)
|
|
|
|
if (props && props !== `null`) {
|
|
|
|
getVNodeCall(codegenNode).arguments[1] = context.hoist(props)
|
|
|
|
}
|
2019-10-05 02:34:26 +08:00
|
|
|
}
|
2019-10-04 11:30:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-08 05:12:22 +08:00
|
|
|
if (child.type === NodeTypes.ELEMENT) {
|
2019-10-05 02:34:26 +08:00
|
|
|
walk(child.children, context, resultCache)
|
2019-10-08 05:12:22 +08:00
|
|
|
} else if (child.type === NodeTypes.FOR) {
|
|
|
|
// Do not hoist v-for single child because it has to be a block
|
|
|
|
walk(child.children, context, resultCache, child.children.length === 1)
|
2019-10-04 11:30:25 +08:00
|
|
|
} else if (child.type === NodeTypes.IF) {
|
|
|
|
for (let i = 0; i < child.branches.length; i++) {
|
2019-10-08 05:12:22 +08:00
|
|
|
const branchChildren = child.branches[i].children
|
|
|
|
// Do not hoist v-if single child because it has to be a block
|
|
|
|
walk(branchChildren, context, resultCache, branchChildren.length === 1)
|
2019-10-04 11:30:25 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-17 00:00:55 +08:00
|
|
|
export function isStaticNode(
|
2019-10-15 23:41:24 +08:00
|
|
|
node: TemplateChildNode | SimpleExpressionNode,
|
2019-10-17 00:00:55 +08:00
|
|
|
resultCache: Map<TemplateChildNode, boolean> = new Map()
|
2019-10-04 11:30:25 +08:00
|
|
|
): boolean {
|
|
|
|
switch (node.type) {
|
|
|
|
case NodeTypes.ELEMENT:
|
2019-10-05 02:34:26 +08:00
|
|
|
if (node.tagType !== ElementTypes.ELEMENT) {
|
|
|
|
return false
|
|
|
|
}
|
2019-10-16 22:46:41 +08:00
|
|
|
const cached = resultCache.get(node)
|
|
|
|
if (cached !== undefined) {
|
|
|
|
return cached
|
2019-10-04 11:30:25 +08:00
|
|
|
}
|
2019-10-24 05:57:40 +08:00
|
|
|
const codegenNode = node.codegenNode!
|
|
|
|
if (codegenNode.type !== NodeTypes.JS_CALL_EXPRESSION) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
const flag = getPatchFlag(codegenNode)
|
2019-10-21 05:00:11 +08:00
|
|
|
if (!flag && !hasDynamicKeyOrRef(node) && !hasCachedProps(node)) {
|
2019-10-04 11:30:25 +08:00
|
|
|
// element self is static. check its children.
|
|
|
|
for (let i = 0; i < node.children.length; i++) {
|
2019-10-05 02:34:26 +08:00
|
|
|
if (!isStaticNode(node.children[i], resultCache)) {
|
|
|
|
resultCache.set(node, false)
|
2019-10-04 11:30:25 +08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2019-10-05 02:34:26 +08:00
|
|
|
resultCache.set(node, true)
|
2019-10-04 11:30:25 +08:00
|
|
|
return true
|
|
|
|
} else {
|
2019-10-21 05:01:01 +08:00
|
|
|
resultCache.set(node, false)
|
2019-10-04 11:30:25 +08:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
case NodeTypes.TEXT:
|
|
|
|
case NodeTypes.COMMENT:
|
|
|
|
return true
|
|
|
|
case NodeTypes.IF:
|
|
|
|
case NodeTypes.FOR:
|
2019-10-15 23:41:24 +08:00
|
|
|
return false
|
2019-10-04 11:30:25 +08:00
|
|
|
case NodeTypes.INTERPOLATION:
|
2019-10-22 03:52:29 +08:00
|
|
|
case NodeTypes.TEXT_CALL:
|
2019-10-15 23:41:24 +08:00
|
|
|
return isStaticNode(node.content, resultCache)
|
|
|
|
case NodeTypes.SIMPLE_EXPRESSION:
|
|
|
|
return node.isConstant
|
2019-10-04 11:30:25 +08:00
|
|
|
case NodeTypes.COMPOUND_EXPRESSION:
|
2019-10-15 23:41:24 +08:00
|
|
|
return node.children.every(child => {
|
|
|
|
return (
|
|
|
|
isString(child) || isSymbol(child) || isStaticNode(child, resultCache)
|
|
|
|
)
|
|
|
|
})
|
2019-10-04 11:30:25 +08:00
|
|
|
default:
|
|
|
|
if (__DEV__) {
|
|
|
|
const exhaustiveCheck: never = node
|
|
|
|
exhaustiveCheck
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2019-10-19 09:51:34 +08:00
|
|
|
|
|
|
|
function hasDynamicKeyOrRef(node: ElementNode): boolean {
|
|
|
|
return !!(findProp(node, 'key', true) || findProp(node, 'ref', true))
|
|
|
|
}
|
|
|
|
|
|
|
|
function hasCachedProps(node: PlainElementNode): boolean {
|
|
|
|
if (__BROWSER__) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
const props = getNodeProps(node)
|
|
|
|
if (
|
|
|
|
props &&
|
|
|
|
props !== 'null' &&
|
|
|
|
props.type === NodeTypes.JS_OBJECT_EXPRESSION
|
|
|
|
) {
|
|
|
|
const { properties } = props
|
|
|
|
for (let i = 0; i < properties.length; i++) {
|
|
|
|
if (properties[i].value.type === NodeTypes.JS_CACHE_EXPRESSION) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-10-24 05:57:40 +08:00
|
|
|
function getNodeProps(node: PlainElementNode) {
|
|
|
|
const codegenNode = node.codegenNode!
|
|
|
|
if (codegenNode.type === NodeTypes.JS_CALL_EXPRESSION) {
|
|
|
|
return getVNodeArgAt(
|
|
|
|
codegenNode,
|
|
|
|
1
|
|
|
|
) as PlainElementCodegenNode['arguments'][1]
|
2019-10-19 09:51:34 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-24 05:57:40 +08:00
|
|
|
type NonCachedCodegenNode =
|
|
|
|
| PlainElementCodegenNode
|
|
|
|
| CodegenNodeWithDirective<PlainElementCodegenNode>
|
|
|
|
|
2019-10-19 09:51:34 +08:00
|
|
|
function getVNodeArgAt(
|
2019-10-24 05:57:40 +08:00
|
|
|
node: NonCachedCodegenNode,
|
2019-10-19 09:51:34 +08:00
|
|
|
index: number
|
|
|
|
): PlainElementCodegenNode['arguments'][number] {
|
|
|
|
return getVNodeCall(node).arguments[index]
|
|
|
|
}
|
|
|
|
|
2019-10-24 05:57:40 +08:00
|
|
|
function getVNodeCall(node: NonCachedCodegenNode) {
|
|
|
|
return node.callee === WITH_DIRECTIVES ? node.arguments[0] : node
|
2019-10-19 09:51:34 +08:00
|
|
|
}
|
|
|
|
|
2019-10-24 05:57:40 +08:00
|
|
|
function getPatchFlag(node: NonCachedCodegenNode): number | undefined {
|
|
|
|
const flag = getVNodeArgAt(node, 3) as string
|
|
|
|
return flag ? parseInt(flag, 10) : undefined
|
2019-10-19 09:51:34 +08:00
|
|
|
}
|