perf: improve VNode creation performance with compiler hints (#3334)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user