perf: improve VNode creation performance with compiler hints (#3334)
This commit is contained in:
@@ -5,13 +5,12 @@ import {
|
||||
CREATE_SLOTS,
|
||||
RENDER_LIST,
|
||||
OPEN_BLOCK,
|
||||
CREATE_BLOCK,
|
||||
FRAGMENT,
|
||||
CREATE_VNODE,
|
||||
WITH_DIRECTIVES
|
||||
} from './runtimeHelpers'
|
||||
import { PropsExpression } from './transforms/transformElement'
|
||||
import { ImportItem, TransformContext } from './transform'
|
||||
import { getVNodeBlockHelper, getVNodeHelper } from './utils'
|
||||
|
||||
// Vue template is a platform-agnostic superset of HTML (syntax only).
|
||||
// More namespaces like SVG and MathML are declared by platform specific
|
||||
@@ -293,6 +292,7 @@ export interface VNodeCall extends Node {
|
||||
directives: DirectiveArguments | undefined
|
||||
isBlock: boolean
|
||||
disableTracking: boolean
|
||||
isComponent: boolean
|
||||
}
|
||||
|
||||
// JS Node Types ---------------------------------------------------------------
|
||||
@@ -560,14 +560,15 @@ export function createVNodeCall(
|
||||
directives?: VNodeCall['directives'],
|
||||
isBlock: VNodeCall['isBlock'] = false,
|
||||
disableTracking: VNodeCall['disableTracking'] = false,
|
||||
isComponent: VNodeCall['isComponent'] = false,
|
||||
loc = locStub
|
||||
): VNodeCall {
|
||||
if (context) {
|
||||
if (isBlock) {
|
||||
context.helper(OPEN_BLOCK)
|
||||
context.helper(CREATE_BLOCK)
|
||||
context.helper(getVNodeBlockHelper(context.inSSR, isComponent))
|
||||
} else {
|
||||
context.helper(CREATE_VNODE)
|
||||
context.helper(getVNodeHelper(context.inSSR, isComponent))
|
||||
}
|
||||
if (directives) {
|
||||
context.helper(WITH_DIRECTIVES)
|
||||
@@ -584,6 +585,7 @@ export function createVNodeCall(
|
||||
directives,
|
||||
isBlock,
|
||||
disableTracking,
|
||||
isComponent,
|
||||
loc
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
||||
import {
|
||||
advancePositionWithMutation,
|
||||
assert,
|
||||
getVNodeBlockHelper,
|
||||
getVNodeHelper,
|
||||
isSimpleIdentifier,
|
||||
toValidAssetId
|
||||
} from './utils'
|
||||
@@ -47,7 +49,7 @@ import {
|
||||
POP_SCOPE_ID,
|
||||
WITH_SCOPE_ID,
|
||||
WITH_DIRECTIVES,
|
||||
CREATE_BLOCK,
|
||||
CREATE_ELEMENT_VNODE,
|
||||
OPEN_BLOCK,
|
||||
CREATE_STATIC,
|
||||
WITH_CTX,
|
||||
@@ -96,7 +98,8 @@ function createCodegenContext(
|
||||
runtimeGlobalName = `Vue`,
|
||||
runtimeModuleName = `vue`,
|
||||
ssr = false,
|
||||
isTS = false
|
||||
isTS = false,
|
||||
inSSR = false
|
||||
}: CodegenOptions
|
||||
): CodegenContext {
|
||||
const context: CodegenContext = {
|
||||
@@ -110,6 +113,7 @@ function createCodegenContext(
|
||||
runtimeModuleName,
|
||||
ssr,
|
||||
isTS,
|
||||
inSSR,
|
||||
source: ast.loc.source,
|
||||
code: ``,
|
||||
column: 1,
|
||||
@@ -218,7 +222,6 @@ export function generate(
|
||||
} else {
|
||||
genFunctionPreamble(ast, preambleContext)
|
||||
}
|
||||
|
||||
// enter render function
|
||||
const functionName = ssr ? `ssrRender` : `render`
|
||||
const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
|
||||
@@ -355,6 +358,7 @@ function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
||||
if (ast.hoists.length) {
|
||||
const staticHelpers = [
|
||||
CREATE_VNODE,
|
||||
CREATE_ELEMENT_VNODE,
|
||||
CREATE_COMMENT,
|
||||
CREATE_TEXT,
|
||||
CREATE_STATIC
|
||||
@@ -754,7 +758,8 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
||||
dynamicProps,
|
||||
directives,
|
||||
isBlock,
|
||||
disableTracking
|
||||
disableTracking,
|
||||
isComponent
|
||||
} = node
|
||||
if (directives) {
|
||||
push(helper(WITH_DIRECTIVES) + `(`)
|
||||
@@ -765,7 +770,10 @@ function genVNodeCall(node: VNodeCall, context: CodegenContext) {
|
||||
if (pure) {
|
||||
push(PURE_ANNOTATION)
|
||||
}
|
||||
push(helper(isBlock ? CREATE_BLOCK : CREATE_VNODE) + `(`, node)
|
||||
const callHelper: symbol = isBlock
|
||||
? getVNodeBlockHelper(context.inSSR, isComponent)
|
||||
: getVNodeHelper(context.inSSR, isComponent)
|
||||
push(helper(callHelper) + `(`, node)
|
||||
genNodeList(
|
||||
genNullableArgs([tag, props, children, patchFlag, dynamicProps]),
|
||||
context
|
||||
|
||||
@@ -122,11 +122,25 @@ interface SharedTransformCodegenOptions {
|
||||
*/
|
||||
prefixIdentifiers?: boolean
|
||||
/**
|
||||
* Generate SSR-optimized render functions instead.
|
||||
* Control whether generate SSR-optimized render functions instead.
|
||||
* The resulting function must be attached to the component via the
|
||||
* `ssrRender` option instead of `render`.
|
||||
*
|
||||
* When compiler generates code for SSR's fallback branch, we need to set it to false:
|
||||
* - context.ssr = false
|
||||
*
|
||||
* see `subTransform` in `ssrTransformCompoent.ts`
|
||||
*/
|
||||
ssr?: boolean
|
||||
/**
|
||||
* Indicates whether the compiler generates code for SSR,
|
||||
* it is always true when generating code for SSR,
|
||||
* regardless of whether we are generating code for SSR's fallback branch,
|
||||
* this means that when the compiler generates code for SSR's fallback branch:
|
||||
* - context.ssr = false
|
||||
* - context.inSSR = true
|
||||
*/
|
||||
inSSR?: boolean
|
||||
/**
|
||||
* Optional binding metadata analyzed from script - used to optimize
|
||||
* binding access when `prefixIdentifiers` is enabled.
|
||||
|
||||
@@ -5,7 +5,9 @@ export const KEEP_ALIVE = Symbol(__DEV__ ? `KeepAlive` : ``)
|
||||
export const BASE_TRANSITION = Symbol(__DEV__ ? `BaseTransition` : ``)
|
||||
export const OPEN_BLOCK = Symbol(__DEV__ ? `openBlock` : ``)
|
||||
export const CREATE_BLOCK = Symbol(__DEV__ ? `createBlock` : ``)
|
||||
export const CREATE_ELEMENT_BLOCK = Symbol(__DEV__ ? `createElementBlock` : ``)
|
||||
export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``)
|
||||
export const CREATE_ELEMENT_VNODE = Symbol(__DEV__ ? `createElementVNode` : ``)
|
||||
export const CREATE_COMMENT = Symbol(__DEV__ ? `createCommentVNode` : ``)
|
||||
export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``)
|
||||
export const CREATE_STATIC = Symbol(__DEV__ ? `createStaticVNode` : ``)
|
||||
@@ -21,6 +23,10 @@ export const RENDER_SLOT = Symbol(__DEV__ ? `renderSlot` : ``)
|
||||
export const CREATE_SLOTS = Symbol(__DEV__ ? `createSlots` : ``)
|
||||
export const TO_DISPLAY_STRING = Symbol(__DEV__ ? `toDisplayString` : ``)
|
||||
export const MERGE_PROPS = Symbol(__DEV__ ? `mergeProps` : ``)
|
||||
export const NORMALIZE_CLASS = Symbol(__DEV__ ? `normalizeClass` : ``)
|
||||
export const NORMALIZE_STYLE = Symbol(__DEV__ ? `normalizeStyle` : ``)
|
||||
export const NORMALIZE_PROPS = Symbol(__DEV__ ? `normalizeProps` : ``)
|
||||
export const GUARD_REACTIVE_PROPS = Symbol(__DEV__ ? `guardReactiveProps` : ``)
|
||||
export const TO_HANDLERS = Symbol(__DEV__ ? `toHandlers` : ``)
|
||||
export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
|
||||
export const CAPITALIZE = Symbol(__DEV__ ? `capitalize` : ``)
|
||||
@@ -44,7 +50,9 @@ export const helperNameMap: any = {
|
||||
[BASE_TRANSITION]: `BaseTransition`,
|
||||
[OPEN_BLOCK]: `openBlock`,
|
||||
[CREATE_BLOCK]: `createBlock`,
|
||||
[CREATE_ELEMENT_BLOCK]: `createElementBlock`,
|
||||
[CREATE_VNODE]: `createVNode`,
|
||||
[CREATE_ELEMENT_VNODE]: `createElementVNode`,
|
||||
[CREATE_COMMENT]: `createCommentVNode`,
|
||||
[CREATE_TEXT]: `createTextVNode`,
|
||||
[CREATE_STATIC]: `createStaticVNode`,
|
||||
@@ -58,6 +66,10 @@ export const helperNameMap: any = {
|
||||
[CREATE_SLOTS]: `createSlots`,
|
||||
[TO_DISPLAY_STRING]: `toDisplayString`,
|
||||
[MERGE_PROPS]: `mergeProps`,
|
||||
[NORMALIZE_CLASS]: `normalizeClass`,
|
||||
[NORMALIZE_STYLE]: `normalizeStyle`,
|
||||
[NORMALIZE_PROPS]: `normalizeProps`,
|
||||
[GUARD_REACTIVE_PROPS]: `guardReactiveProps`,
|
||||
[TO_HANDLERS]: `toHandlers`,
|
||||
[CAMELIZE]: `camelize`,
|
||||
[CAPITALIZE]: `capitalize`,
|
||||
|
||||
@@ -33,12 +33,10 @@ import {
|
||||
TO_DISPLAY_STRING,
|
||||
FRAGMENT,
|
||||
helperNameMap,
|
||||
CREATE_BLOCK,
|
||||
CREATE_COMMENT,
|
||||
OPEN_BLOCK,
|
||||
CREATE_VNODE
|
||||
OPEN_BLOCK
|
||||
} from './runtimeHelpers'
|
||||
import { isVSlot } from './utils'
|
||||
import { getVNodeBlockHelper, getVNodeHelper, isVSlot } from './utils'
|
||||
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
|
||||
import { CompilerCompatOptions } from './compat/compatConfig'
|
||||
|
||||
@@ -139,6 +137,7 @@ export function createTransformContext(
|
||||
scopeId = null,
|
||||
slotted = true,
|
||||
ssr = false,
|
||||
inSSR = false,
|
||||
ssrCssVars = ``,
|
||||
bindingMetadata = EMPTY_OBJ,
|
||||
inline = false,
|
||||
@@ -164,6 +163,7 @@ export function createTransformContext(
|
||||
scopeId,
|
||||
slotted,
|
||||
ssr,
|
||||
inSSR,
|
||||
ssrCssVars,
|
||||
bindingMetadata,
|
||||
inline,
|
||||
@@ -346,10 +346,10 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
||||
const codegenNode = child.codegenNode
|
||||
if (codegenNode.type === NodeTypes.VNODE_CALL) {
|
||||
if (!codegenNode.isBlock) {
|
||||
removeHelper(CREATE_VNODE)
|
||||
codegenNode.isBlock = true
|
||||
removeHelper(getVNodeHelper(context.inSSR, codegenNode.isComponent))
|
||||
helper(OPEN_BLOCK)
|
||||
helper(CREATE_BLOCK)
|
||||
helper(getVNodeBlockHelper(context.inSSR, codegenNode.isComponent))
|
||||
}
|
||||
}
|
||||
root.codegenNode = codegenNode
|
||||
@@ -380,7 +380,9 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
||||
patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
true,
|
||||
undefined,
|
||||
false /* isComponent */
|
||||
)
|
||||
} else {
|
||||
// no children = noop. codegen will return null.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -30,9 +30,16 @@ import {
|
||||
SUSPENSE,
|
||||
KEEP_ALIVE,
|
||||
BASE_TRANSITION,
|
||||
TO_HANDLERS
|
||||
TO_HANDLERS,
|
||||
NORMALIZE_PROPS,
|
||||
GUARD_REACTIVE_PROPS,
|
||||
CREATE_BLOCK,
|
||||
CREATE_ELEMENT_BLOCK,
|
||||
CREATE_VNODE,
|
||||
CREATE_ELEMENT_VNODE
|
||||
} from './runtimeHelpers'
|
||||
import { isString, isObject, hyphenate, extend } from '@vue/shared'
|
||||
import { PropsExpression } from './transforms/transformElement'
|
||||
|
||||
export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
|
||||
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
|
||||
@@ -291,14 +298,66 @@ export function isSlotOutlet(
|
||||
return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT
|
||||
}
|
||||
|
||||
export function getVNodeHelper(ssr: boolean, isComponent: boolean) {
|
||||
return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE
|
||||
}
|
||||
|
||||
export function getVNodeBlockHelper(ssr: boolean, isComponent: boolean) {
|
||||
return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK
|
||||
}
|
||||
|
||||
const propsHelperSet = new Set([NORMALIZE_PROPS, GUARD_REACTIVE_PROPS])
|
||||
|
||||
function getUnnormalizedProps(
|
||||
props: PropsExpression | '{}',
|
||||
callPath: CallExpression[] = []
|
||||
): [PropsExpression | '{}', CallExpression[]] {
|
||||
if (
|
||||
props &&
|
||||
!isString(props) &&
|
||||
props.type === NodeTypes.JS_CALL_EXPRESSION
|
||||
) {
|
||||
const callee = props.callee
|
||||
if (!isString(callee) && propsHelperSet.has(callee)) {
|
||||
return getUnnormalizedProps(
|
||||
props.arguments[0] as PropsExpression,
|
||||
callPath.concat(props)
|
||||
)
|
||||
}
|
||||
}
|
||||
return [props, callPath]
|
||||
}
|
||||
export function injectProp(
|
||||
node: VNodeCall | RenderSlotCall,
|
||||
prop: Property,
|
||||
context: TransformContext
|
||||
) {
|
||||
let propsWithInjection: ObjectExpression | CallExpression | undefined
|
||||
const props =
|
||||
const originalProps =
|
||||
node.type === NodeTypes.VNODE_CALL ? node.props : node.arguments[2]
|
||||
|
||||
/**
|
||||
* 1. mergeProps(...)
|
||||
* 2. toHandlers(...)
|
||||
* 3. normalizeProps(...)
|
||||
* 4. normalizeProps(guardReactiveProps(...))
|
||||
*
|
||||
* we need to get the real props before normalization
|
||||
*/
|
||||
let props = originalProps
|
||||
let callPath: CallExpression[] = []
|
||||
let parentCall: CallExpression | undefined
|
||||
if (
|
||||
props &&
|
||||
!isString(props) &&
|
||||
props.type === NodeTypes.JS_CALL_EXPRESSION
|
||||
) {
|
||||
const ret = getUnnormalizedProps(props)
|
||||
props = ret[0]
|
||||
callPath = ret[1]
|
||||
parentCall = callPath[callPath.length - 1]
|
||||
}
|
||||
|
||||
if (props == null || isString(props)) {
|
||||
propsWithInjection = createObjectExpression([prop])
|
||||
} else if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
|
||||
@@ -341,11 +400,25 @@ export function injectProp(
|
||||
createObjectExpression([prop]),
|
||||
props
|
||||
])
|
||||
// in the case of nested helper call, e.g. `normalizeProps(guardReactiveProps(props))`,
|
||||
// it will be rewritten as `normalizeProps(mergeProps({ key: 0 }, props))`,
|
||||
// the `guardReactiveProps` will no longer be needed
|
||||
if (parentCall && parentCall.callee === GUARD_REACTIVE_PROPS) {
|
||||
parentCall = callPath[callPath.length - 2]
|
||||
}
|
||||
}
|
||||
if (node.type === NodeTypes.VNODE_CALL) {
|
||||
node.props = propsWithInjection
|
||||
if (parentCall) {
|
||||
parentCall.arguments[0] = propsWithInjection
|
||||
} else {
|
||||
node.props = propsWithInjection
|
||||
}
|
||||
} else {
|
||||
node.arguments[2] = propsWithInjection
|
||||
if (parentCall) {
|
||||
parentCall.arguments[0] = propsWithInjection
|
||||
} else {
|
||||
node.arguments[2] = propsWithInjection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user