|
|
@@ -1,4 +1,5 @@
|
|
|
import {
|
|
|
+ ConstantTypes,
|
|
|
RootNode,
|
|
|
NodeTypes,
|
|
|
TemplateChildNode,
|
|
|
@@ -37,16 +38,10 @@ export function isSingleElementRoot(
|
|
|
)
|
|
|
}
|
|
|
|
|
|
-const enum StaticType {
|
|
|
- NOT_STATIC = 0,
|
|
|
- FULL_STATIC,
|
|
|
- HAS_RUNTIME_CONSTANT
|
|
|
-}
|
|
|
-
|
|
|
function walk(
|
|
|
node: ParentNode,
|
|
|
context: TransformContext,
|
|
|
- resultCache: Map<TemplateChildNode, StaticType>,
|
|
|
+ resultCache: Map<TemplateChildNode, ConstantTypes>,
|
|
|
doNotHoistNode: boolean = false
|
|
|
) {
|
|
|
let hasHoistedNode = false
|
|
|
@@ -58,7 +53,7 @@ function walk(
|
|
|
// @vue/compiler-dom), but doing it here allows us to perform only one full
|
|
|
// walk of the AST and allow `stringifyStatic` to stop walking as soon as its
|
|
|
// stringficiation threshold is met.
|
|
|
- let hasRuntimeConstant = false
|
|
|
+ let canStringify = true
|
|
|
|
|
|
const { children } = node
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
@@ -68,20 +63,20 @@ function walk(
|
|
|
child.type === NodeTypes.ELEMENT &&
|
|
|
child.tagType === ElementTypes.ELEMENT
|
|
|
) {
|
|
|
- let staticType
|
|
|
- if (
|
|
|
- !doNotHoistNode &&
|
|
|
- (staticType = getStaticType(child, resultCache)) > 0
|
|
|
- ) {
|
|
|
- if (staticType === StaticType.HAS_RUNTIME_CONSTANT) {
|
|
|
- hasRuntimeConstant = true
|
|
|
+ const constantType = doNotHoistNode
|
|
|
+ ? ConstantTypes.NOT_CONSTANT
|
|
|
+ : getConstantType(child, resultCache)
|
|
|
+ if (constantType > ConstantTypes.NOT_CONSTANT) {
|
|
|
+ if (constantType < ConstantTypes.CAN_STRINGIFY) {
|
|
|
+ canStringify = false
|
|
|
+ }
|
|
|
+ if (constantType >= ConstantTypes.CAN_HOIST) {
|
|
|
+ ;(child.codegenNode as VNodeCall).patchFlag =
|
|
|
+ PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
|
|
|
+ child.codegenNode = context.hoist(child.codegenNode!)
|
|
|
+ hasHoistedNode = true
|
|
|
+ continue
|
|
|
}
|
|
|
- // whole tree is static
|
|
|
- ;(child.codegenNode as VNodeCall).patchFlag =
|
|
|
- PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
|
|
|
- child.codegenNode = context.hoist(child.codegenNode!)
|
|
|
- hasHoistedNode = true
|
|
|
- continue
|
|
|
} else {
|
|
|
// node may contain dynamic children, but its props may be eligible for
|
|
|
// hoisting.
|
|
|
@@ -92,7 +87,8 @@ function walk(
|
|
|
(!flag ||
|
|
|
flag === PatchFlags.NEED_PATCH ||
|
|
|
flag === PatchFlags.TEXT) &&
|
|
|
- !hasNonHoistableProps(child)
|
|
|
+ getGeneratedPropsConstantType(child, resultCache) >=
|
|
|
+ ConstantTypes.CAN_HOIST
|
|
|
) {
|
|
|
const props = getNodeProps(child)
|
|
|
if (props) {
|
|
|
@@ -102,13 +98,15 @@ function walk(
|
|
|
}
|
|
|
}
|
|
|
} else if (child.type === NodeTypes.TEXT_CALL) {
|
|
|
- const staticType = getStaticType(child.content, resultCache)
|
|
|
- if (staticType > 0) {
|
|
|
- if (staticType === StaticType.HAS_RUNTIME_CONSTANT) {
|
|
|
- hasRuntimeConstant = true
|
|
|
+ const contentType = getConstantType(child.content, resultCache)
|
|
|
+ if (contentType > 0) {
|
|
|
+ if (contentType < ConstantTypes.CAN_STRINGIFY) {
|
|
|
+ canStringify = false
|
|
|
+ }
|
|
|
+ if (contentType >= ConstantTypes.CAN_HOIST) {
|
|
|
+ child.codegenNode = context.hoist(child.codegenNode)
|
|
|
+ hasHoistedNode = true
|
|
|
}
|
|
|
- child.codegenNode = context.hoist(child.codegenNode)
|
|
|
- hasHoistedNode = true
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -131,19 +129,19 @@ function walk(
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (!hasRuntimeConstant && hasHoistedNode && context.transformHoist) {
|
|
|
+ if (canStringify && hasHoistedNode && context.transformHoist) {
|
|
|
context.transformHoist(children, context, node)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-export function getStaticType(
|
|
|
+export function getConstantType(
|
|
|
node: TemplateChildNode | SimpleExpressionNode,
|
|
|
- resultCache: Map<TemplateChildNode, StaticType> = new Map()
|
|
|
-): StaticType {
|
|
|
+ resultCache: Map<TemplateChildNode, ConstantTypes> = new Map()
|
|
|
+): ConstantTypes {
|
|
|
switch (node.type) {
|
|
|
case NodeTypes.ELEMENT:
|
|
|
if (node.tagType !== ElementTypes.ELEMENT) {
|
|
|
- return StaticType.NOT_STATIC
|
|
|
+ return ConstantTypes.NOT_CONSTANT
|
|
|
}
|
|
|
const cached = resultCache.get(node)
|
|
|
if (cached !== undefined) {
|
|
|
@@ -151,40 +149,64 @@ export function getStaticType(
|
|
|
}
|
|
|
const codegenNode = node.codegenNode!
|
|
|
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
|
|
|
- return StaticType.NOT_STATIC
|
|
|
+ return ConstantTypes.NOT_CONSTANT
|
|
|
}
|
|
|
const flag = getPatchFlag(codegenNode)
|
|
|
- if (!flag && !hasNonHoistableProps(node)) {
|
|
|
- // element self is static. check its children.
|
|
|
- let returnType = StaticType.FULL_STATIC
|
|
|
+ if (!flag) {
|
|
|
+ let returnType = ConstantTypes.CAN_STRINGIFY
|
|
|
+
|
|
|
+ // Element itself has no patch flag. However we still need to check:
|
|
|
+
|
|
|
+ // 1. Even for a node with no patch flag, it is possible for it to contain
|
|
|
+ // non-hoistable expressions that refers to scope variables, e.g. compiler
|
|
|
+ // injected keys or cached event handlers. Therefore we need to always
|
|
|
+ // check the codegenNode's props to be sure.
|
|
|
+ const generatedPropsType = getGeneratedPropsConstantType(
|
|
|
+ node,
|
|
|
+ resultCache
|
|
|
+ )
|
|
|
+ if (generatedPropsType === ConstantTypes.NOT_CONSTANT) {
|
|
|
+ resultCache.set(node, ConstantTypes.NOT_CONSTANT)
|
|
|
+ return ConstantTypes.NOT_CONSTANT
|
|
|
+ }
|
|
|
+ if (generatedPropsType < returnType) {
|
|
|
+ returnType = generatedPropsType
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. its children.
|
|
|
for (let i = 0; i < node.children.length; i++) {
|
|
|
- const childType = getStaticType(node.children[i], resultCache)
|
|
|
- if (childType === StaticType.NOT_STATIC) {
|
|
|
- resultCache.set(node, StaticType.NOT_STATIC)
|
|
|
- return StaticType.NOT_STATIC
|
|
|
- } else if (childType === StaticType.HAS_RUNTIME_CONSTANT) {
|
|
|
- returnType = StaticType.HAS_RUNTIME_CONSTANT
|
|
|
+ const childType = getConstantType(node.children[i], resultCache)
|
|
|
+ if (childType === ConstantTypes.NOT_CONSTANT) {
|
|
|
+ resultCache.set(node, ConstantTypes.NOT_CONSTANT)
|
|
|
+ return ConstantTypes.NOT_CONSTANT
|
|
|
+ }
|
|
|
+ if (childType < returnType) {
|
|
|
+ returnType = childType
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // check if any of the props contain runtime constants
|
|
|
- if (returnType !== StaticType.HAS_RUNTIME_CONSTANT) {
|
|
|
+ // 3. if the type is not already CAN_SKIP_PATCH which is the lowest non-0
|
|
|
+ // type, check if any of the props can cause the type to be lowered
|
|
|
+ // we can skip can_patch because it's guaranteed by the absence of a
|
|
|
+ // patchFlag.
|
|
|
+ if (returnType > ConstantTypes.CAN_SKIP_PATCH) {
|
|
|
for (let i = 0; i < node.props.length; i++) {
|
|
|
const p = node.props[i]
|
|
|
- if (
|
|
|
- p.type === NodeTypes.DIRECTIVE &&
|
|
|
- p.name === 'bind' &&
|
|
|
- p.exp &&
|
|
|
- (p.exp.type === NodeTypes.COMPOUND_EXPRESSION ||
|
|
|
- p.exp.isRuntimeConstant)
|
|
|
- ) {
|
|
|
- returnType = StaticType.HAS_RUNTIME_CONSTANT
|
|
|
+ if (p.type === NodeTypes.DIRECTIVE && p.name === 'bind' && p.exp) {
|
|
|
+ const expType = getConstantType(p.exp, resultCache)
|
|
|
+ if (expType === ConstantTypes.NOT_CONSTANT) {
|
|
|
+ resultCache.set(node, ConstantTypes.NOT_CONSTANT)
|
|
|
+ return ConstantTypes.NOT_CONSTANT
|
|
|
+ }
|
|
|
+ if (expType < returnType) {
|
|
|
+ returnType = expType
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// only svg/foreignObject could be block here, however if they are
|
|
|
- // stati then they don't need to be blocks since there will be no
|
|
|
+ // static then they don't need to be blocks since there will be no
|
|
|
// nested updates.
|
|
|
if (codegenNode.isBlock) {
|
|
|
codegenNode.isBlock = false
|
|
|
@@ -193,37 +215,33 @@ export function getStaticType(
|
|
|
resultCache.set(node, returnType)
|
|
|
return returnType
|
|
|
} else {
|
|
|
- resultCache.set(node, StaticType.NOT_STATIC)
|
|
|
- return StaticType.NOT_STATIC
|
|
|
+ resultCache.set(node, ConstantTypes.NOT_CONSTANT)
|
|
|
+ return ConstantTypes.NOT_CONSTANT
|
|
|
}
|
|
|
case NodeTypes.TEXT:
|
|
|
case NodeTypes.COMMENT:
|
|
|
- return StaticType.FULL_STATIC
|
|
|
+ return ConstantTypes.CAN_STRINGIFY
|
|
|
case NodeTypes.IF:
|
|
|
case NodeTypes.FOR:
|
|
|
case NodeTypes.IF_BRANCH:
|
|
|
- return StaticType.NOT_STATIC
|
|
|
+ return ConstantTypes.NOT_CONSTANT
|
|
|
case NodeTypes.INTERPOLATION:
|
|
|
case NodeTypes.TEXT_CALL:
|
|
|
- return getStaticType(node.content, resultCache)
|
|
|
+ return getConstantType(node.content, resultCache)
|
|
|
case NodeTypes.SIMPLE_EXPRESSION:
|
|
|
- return node.isRuntimeConstant
|
|
|
- ? StaticType.HAS_RUNTIME_CONSTANT
|
|
|
- : node.isConstant
|
|
|
- ? StaticType.FULL_STATIC
|
|
|
- : StaticType.NOT_STATIC
|
|
|
+ return node.constType
|
|
|
case NodeTypes.COMPOUND_EXPRESSION:
|
|
|
- let returnType = StaticType.FULL_STATIC
|
|
|
+ let returnType = ConstantTypes.CAN_STRINGIFY
|
|
|
for (let i = 0; i < node.children.length; i++) {
|
|
|
const child = node.children[i]
|
|
|
if (isString(child) || isSymbol(child)) {
|
|
|
continue
|
|
|
}
|
|
|
- const childType = getStaticType(child, resultCache)
|
|
|
- if (childType === StaticType.NOT_STATIC) {
|
|
|
- return StaticType.NOT_STATIC
|
|
|
- } else if (childType === StaticType.HAS_RUNTIME_CONSTANT) {
|
|
|
- returnType = StaticType.HAS_RUNTIME_CONSTANT
|
|
|
+ const childType = getConstantType(child, resultCache)
|
|
|
+ if (childType === ConstantTypes.NOT_CONSTANT) {
|
|
|
+ return ConstantTypes.NOT_CONSTANT
|
|
|
+ } else if (childType < returnType) {
|
|
|
+ returnType = childType
|
|
|
}
|
|
|
}
|
|
|
return returnType
|
|
|
@@ -232,33 +250,40 @@ export function getStaticType(
|
|
|
const exhaustiveCheck: never = node
|
|
|
exhaustiveCheck
|
|
|
}
|
|
|
- return StaticType.NOT_STATIC
|
|
|
+ return ConstantTypes.NOT_CONSTANT
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * Even for a node with no patch flag, it is possible for it to contain
|
|
|
- * non-hoistable expressions that refers to scope variables, e.g. compiler
|
|
|
- * injected keys or cached event handlers. Therefore we need to always check the
|
|
|
- * codegenNode's props to be sure.
|
|
|
- */
|
|
|
-function hasNonHoistableProps(node: PlainElementNode): boolean {
|
|
|
+function getGeneratedPropsConstantType(
|
|
|
+ node: PlainElementNode,
|
|
|
+ resultCache: Map<TemplateChildNode, ConstantTypes>
|
|
|
+): ConstantTypes {
|
|
|
+ let returnType = ConstantTypes.CAN_STRINGIFY
|
|
|
const props = getNodeProps(node)
|
|
|
if (props && props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
|
|
|
const { properties } = props
|
|
|
for (let i = 0; i < properties.length; i++) {
|
|
|
const { key, value } = properties[i]
|
|
|
- if (
|
|
|
- key.type !== NodeTypes.SIMPLE_EXPRESSION ||
|
|
|
- !key.isStatic ||
|
|
|
- (value.type !== NodeTypes.SIMPLE_EXPRESSION ||
|
|
|
- (!value.isStatic && !value.isConstant))
|
|
|
- ) {
|
|
|
- return true
|
|
|
+ const keyType = getConstantType(key, resultCache)
|
|
|
+ if (keyType === ConstantTypes.NOT_CONSTANT) {
|
|
|
+ return keyType
|
|
|
+ }
|
|
|
+ if (keyType < returnType) {
|
|
|
+ returnType = keyType
|
|
|
+ }
|
|
|
+ if (value.type !== NodeTypes.SIMPLE_EXPRESSION) {
|
|
|
+ return ConstantTypes.NOT_CONSTANT
|
|
|
+ }
|
|
|
+ const valueType = getConstantType(value, resultCache)
|
|
|
+ if (valueType === ConstantTypes.NOT_CONSTANT) {
|
|
|
+ return valueType
|
|
|
+ }
|
|
|
+ if (valueType < returnType) {
|
|
|
+ returnType = valueType
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- return false
|
|
|
+ return returnType
|
|
|
}
|
|
|
|
|
|
function getNodeProps(node: PlainElementNode) {
|