perf: improve VNode creation performance with compiler hints (#3334)

This commit is contained in:
HcySunYang
2021-06-23 07:15:20 +08:00
committed by Evan You
parent 31abdc8ada
commit ceff89905b
42 changed files with 1130 additions and 685 deletions

View File

@@ -9,12 +9,20 @@ import {
ComponentNode,
TemplateNode,
VNodeCall,
ParentNode
ParentNode,
JSChildNode,
CallExpression
} from '../ast'
import { TransformContext } from '../transform'
import { PatchFlags, isString, isSymbol } from '@vue/shared'
import { isSlotOutlet } from '../utils'
import { CREATE_BLOCK, CREATE_VNODE, OPEN_BLOCK } from '../runtimeHelpers'
import { getVNodeBlockHelper, getVNodeHelper, isSlotOutlet } from '../utils'
import {
OPEN_BLOCK,
GUARD_REACTIVE_PROPS,
NORMALIZE_CLASS,
NORMALIZE_PROPS,
NORMALIZE_STYLE
} from '../runtimeHelpers'
export function hoistStatic(root: RootNode, context: TransformContext) {
walk(
@@ -213,9 +221,11 @@ export function getConstantType(
// nested updates.
if (codegenNode.isBlock) {
context.removeHelper(OPEN_BLOCK)
context.removeHelper(CREATE_BLOCK)
context.removeHelper(
getVNodeBlockHelper(context.inSSR, codegenNode.isComponent)
)
codegenNode.isBlock = false
context.helper(CREATE_VNODE)
context.helper(getVNodeHelper(context.inSSR, codegenNode.isComponent))
}
constantCache.set(node, returnType)
@@ -260,6 +270,33 @@ export function getConstantType(
}
}
const allowHoistedHelperSet = new Set([
NORMALIZE_CLASS,
NORMALIZE_STYLE,
NORMALIZE_PROPS,
GUARD_REACTIVE_PROPS
])
function getConstantTypeOfHelperCall(
value: CallExpression,
context: TransformContext
): ConstantTypes {
if (
value.type === NodeTypes.JS_CALL_EXPRESSION &&
!isString(value.callee) &&
allowHoistedHelperSet.has(value.callee)
) {
const arg = value.arguments[0] as JSChildNode
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
return getConstantType(arg, context)
} else if (arg.type === NodeTypes.JS_CALL_EXPRESSION) {
// in the case of nested helper call, e.g. `normalizeProps(guardReactiveProps(exp))`
return getConstantTypeOfHelperCall(arg, context)
}
}
return ConstantTypes.NOT_CONSTANT
}
function getGeneratedPropsConstantType(
node: PlainElementNode,
context: TransformContext
@@ -278,6 +315,12 @@ function getGeneratedPropsConstantType(
returnType = keyType
}
if (value.type !== NodeTypes.SIMPLE_EXPRESSION) {
// some helper calls can be hoisted,
// such as the `normalizeProps` generated by the compiler for pre-normalize class,
// in this case we need to respect the ConstanType of the helper's argments
if (value.type === NodeTypes.JS_CALL_EXPRESSION) {
return getConstantTypeOfHelperCall(value, context)
}
return ConstantTypes.NOT_CONSTANT
}
const valueType = getConstantType(value, context)

View File

@@ -37,11 +37,15 @@ import {
RESOLVE_COMPONENT,
RESOLVE_DYNAMIC_COMPONENT,
MERGE_PROPS,
NORMALIZE_CLASS,
NORMALIZE_STYLE,
NORMALIZE_PROPS,
TO_HANDLERS,
TELEPORT,
KEEP_ALIVE,
SUSPENSE,
UNREF
UNREF,
GUARD_REACTIVE_PROPS
} from '../runtimeHelpers'
import {
getInnerRange,
@@ -226,6 +230,7 @@ export const transformElement: NodeTransform = (node, context) => {
vnodeDirectives,
!!shouldUseBlock,
false /* disableTracking */,
isComponent,
node.loc
)
}
@@ -418,15 +423,25 @@ export function buildProps(
// skip if the prop is a cached handler or has constant value
return
}
if (name === 'ref') {
hasRef = true
} else if (name === 'class' && !isComponent) {
} else if (name === 'class') {
hasClassBinding = true
} else if (name === 'style' && !isComponent) {
} else if (name === 'style') {
hasStyleBinding = true
} else if (name !== 'key' && !dynamicPropNames.includes(name)) {
dynamicPropNames.push(name)
}
// treat the dynamic class and style binding of the component as dynamic props
if (
isComponent &&
(name === 'class' || name === 'style') &&
!dynamicPropNames.includes(name)
) {
dynamicPropNames.push(name)
}
} else {
hasDynamicKeys = true
}
@@ -657,10 +672,10 @@ export function buildProps(
if (hasDynamicKeys) {
patchFlag |= PatchFlags.FULL_PROPS
} else {
if (hasClassBinding) {
if (hasClassBinding && !isComponent) {
patchFlag |= PatchFlags.CLASS
}
if (hasStyleBinding) {
if (hasStyleBinding && !isComponent) {
patchFlag |= PatchFlags.STYLE
}
if (dynamicPropNames.length) {
@@ -677,6 +692,72 @@ export function buildProps(
patchFlag |= PatchFlags.NEED_PATCH
}
// pre-normalize props, SSR is skipped for now
if (!context.inSSR && propsExpression) {
switch (propsExpression.type) {
case NodeTypes.JS_OBJECT_EXPRESSION:
// means that there is no v-bind,
// but still need to deal with dynamic key binding
let classKeyIndex = -1
let styleKeyIndex = -1
let dynamicKeyIndex = -1
for (let i = 0; i < propsExpression.properties.length; i++) {
const p = propsExpression.properties[i]
if (p.key.type !== NodeTypes.SIMPLE_EXPRESSION) continue
if (!isStaticExp(p.key)) dynamicKeyIndex = i
if (isStaticExp(p.key) && p.key.content === 'class') classKeyIndex = i
if (isStaticExp(p.key) && p.key.content === 'style') styleKeyIndex = i
}
const classProp = propsExpression.properties[classKeyIndex]
const styleProp = propsExpression.properties[styleKeyIndex]
// no dynamic key
if (dynamicKeyIndex === -1) {
if (classProp && !isStaticExp(classProp.value)) {
classProp.value = createCallExpression(
context.helper(NORMALIZE_CLASS),
[classProp.value]
)
}
if (
styleProp &&
!isStaticExp(styleProp.value) &&
// the static style is compiled into an object,
// so use `hasStyleBinding` to ensure that it is a dynamic style binding
hasStyleBinding
) {
styleProp.value = createCallExpression(
context.helper(NORMALIZE_STYLE),
[styleProp.value]
)
}
} else {
// dynamic key binding, wrap with `normalizeProps`
propsExpression = createCallExpression(
context.helper(NORMALIZE_PROPS),
[propsExpression]
)
}
break
case NodeTypes.JS_CALL_EXPRESSION:
// mergeProps call, do nothing
break
default:
// single v-bind
propsExpression = createCallExpression(
context.helper(NORMALIZE_PROPS),
[
createCallExpression(context.helper(GUARD_REACTIVE_PROPS), [
propsExpression
])
]
)
break
}
}
return {
props: propsExpression,
directives: runtimeDirectives,

View File

@@ -32,15 +32,11 @@ import {
findProp,
isTemplateNode,
isSlotOutlet,
injectProp
injectProp,
getVNodeBlockHelper,
getVNodeHelper
} from '../utils'
import {
RENDER_LIST,
OPEN_BLOCK,
CREATE_BLOCK,
FRAGMENT,
CREATE_VNODE
} from '../runtimeHelpers'
import { RENDER_LIST, OPEN_BLOCK, FRAGMENT } from '../runtimeHelpers'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
@@ -85,6 +81,7 @@ export const transformFor = createStructuralDirectiveTransform(
: keyProp
? PatchFlags.KEYED_FRAGMENT
: PatchFlags.UNKEYED_FRAGMENT
forNode.codegenNode = createVNodeCall(
context,
helper(FRAGMENT),
@@ -96,6 +93,7 @@ export const transformFor = createStructuralDirectiveTransform(
undefined,
true /* isBlock */,
!isStableFragment /* disableTracking */,
false /* isComponent */,
node.loc
) as ForCodegenNode
@@ -156,7 +154,9 @@ export const transformFor = createStructuralDirectiveTransform(
: ``),
undefined,
undefined,
true
true,
undefined,
false /* isComponent */
)
} else {
// Normal element v-for. Directly use the child's codegenNode
@@ -170,18 +170,22 @@ export const transformFor = createStructuralDirectiveTransform(
if (childBlock.isBlock) {
// switch from block to vnode
removeHelper(OPEN_BLOCK)
removeHelper(CREATE_BLOCK)
removeHelper(
getVNodeBlockHelper(context.inSSR, childBlock.isComponent)
)
} else {
// switch from vnode to block
removeHelper(CREATE_VNODE)
removeHelper(
getVNodeHelper(context.inSSR, childBlock.isComponent)
)
}
}
childBlock.isBlock = !isStableFragment
if (childBlock.isBlock) {
helper(OPEN_BLOCK)
helper(CREATE_BLOCK)
helper(getVNodeBlockHelper(context.inSSR, childBlock.isComponent))
} else {
helper(CREATE_VNODE)
helper(getVNodeHelper(context.inSSR, childBlock.isComponent))
}
}

View File

@@ -27,14 +27,15 @@ import {
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import { validateBrowserExpression } from '../validateExpression'
import { FRAGMENT, CREATE_COMMENT, OPEN_BLOCK } from '../runtimeHelpers'
import {
CREATE_BLOCK,
FRAGMENT,
CREATE_COMMENT,
OPEN_BLOCK,
CREATE_VNODE
} from '../runtimeHelpers'
import { injectProp, findDir, findProp, isBuiltInType } from '../utils'
injectProp,
findDir,
findProp,
isBuiltInType,
getVNodeHelper,
getVNodeBlockHelper
} from '../utils'
import { PatchFlags, PatchFlagNames } from '@vue/shared'
export const transformIf = createStructuralDirectiveTransform(
@@ -278,6 +279,7 @@ function createChildrenCodegenNode(
undefined,
true,
false,
false /* isComponent */,
branch.loc
)
}
@@ -286,10 +288,10 @@ function createChildrenCodegenNode(
.codegenNode as BlockCodegenNode
// Change createVNode to createBlock.
if (vnodeCall.type === NodeTypes.VNODE_CALL && !vnodeCall.isBlock) {
removeHelper(CREATE_VNODE)
removeHelper(getVNodeHelper(context.inSSR, vnodeCall.isComponent))
vnodeCall.isBlock = true
helper(OPEN_BLOCK)
helper(CREATE_BLOCK)
helper(getVNodeBlockHelper(context.inSSR, vnodeCall.isComponent))
}
// inject branch key
injectProp(vnodeCall, keyProperty, context)