function stringifyDynamicPropNames(props: string[]): string {
let propsNamesString = `[`
for (let i = 0, l = props.length; i < l; i++) {
propsNamesString += JSON.stringify(props[i])
if (i < l - 1) propsNamesString += ', '
}
return propsNamesString + `]`
}
export function buildProps(
node: ElementNode,
context: TransformContext,
props: ElementNode['props'] = node.props,
ssr = false
): {
props: PropsExpression | undefined
directives: DirectiveNode[]
patchFlag: number
dynamicPropNames: string[]
} {
const { tag, loc: elementLoc } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
let properties: ObjectExpression['properties'] = []
const mergeArgs: PropsExpression[] = []
const runtimeDirectives: DirectiveNode[] = []
let patchFlag = 0
let hasRef = false
let hasClassBinding = false
let hasStyleBinding = false
let hasHydrationEventBinding = false
let hasDynamicKeys = false
let hasVnodeHook = false
const dynamicPropNames: string[] = []
const analyzePatchFlag = ({ key, value }: Property) => {
if (isStaticExp(key)) {
const name = key.content
const isEventHandler = isOn(name)
if (isEventHandler && isReservedProp(name)) {
hasVnodeHook = true
}
if (
value.type === NodeTypes.JS_CACHE_EXPRESSION ||
((value.type === NodeTypes.SIMPLE_EXPRESSION ||
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
getConstantType(value, context) > 0)
) {
return
}
if (name === 'ref') {
hasRef = true
} else if (name === 'class' && !isComponent) {
hasClassBinding = true
} else if (name === 'style' && !isComponent) {
hasStyleBinding = true
} else if (name !== 'key' && !dynamicPropNames.includes(name)) {
dynamicPropNames.push(name)
}
} else {
hasDynamicKeys = true
}
}
for (let i = 0; i < props.length; i++) {
const prop = props[i]
if (prop.type === NodeTypes.ATTRIBUTE) {
const { loc, name, value } = prop
let isStatic = true
if (name === 'ref') {
hasRef = true
if (!__BROWSER__ && context.inline) {
isStatic = false
}
}
if (
name === 'is' &&
(isComponentTag(tag) || (value && value.content.startsWith('vue:')))
) {
continue
}
properties.push(
createObjectProperty(
createSimpleExpression(
name,
true,
getInnerRange(loc, 0, name.length)
),
createSimpleExpression(
value ? value.content : '',
isStatic,
value ? value.loc : loc
)
)
)
} else {
const { name, arg, exp, loc } = prop
const isVBind = name === 'bind'
const isVOn = name === 'on'
if (name === 'slot') {
if (!isComponent) {
context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, loc)
)
}
continue
}
if (name === 'once') {
continue
}
if (
name === 'is' ||
(isVBind && isComponentTag(tag) && isBindKey(arg, 'is'))
) {
continue
}
if (isVOn && ssr) {
continue
}
if (!arg && (isVBind || isVOn)) {
hasDynamicKeys = true
if (exp) {
if (properties.length) {
mergeArgs.push(
createObjectExpression(dedupeProperties(properties), elementLoc)
)
properties = []
}
if (isVBind) {
} else {
mergeArgs.push({
type: NodeTypes.JS_CALL_EXPRESSION,
loc,
callee: context.helper(TO_HANDLERS),
arguments: [exp]
})
}
} else {
context.onError(
createCompilerError(
isVBind
? ErrorCodes.X_V_BIND_NO_EXPRESSION
: ErrorCodes.X_V_ON_NO_EXPRESSION,
loc
)
)
}
continue
}
const directiveTransform = context.directiveTransforms[name]
if (directiveTransform) {
const { props, needRuntime } = directiveTransform(prop, node, context)
!ssr && props.forEach(analyzePatchFlag)
properties.push(...props)
if (needRuntime) {
runtimeDirectives.push(prop)
if (isSymbol(needRuntime)) {
directiveImportMap.set(prop, needRuntime)
}
}
} else {
runtimeDirectives.push(prop)
}
}
if (
__COMPAT__ &&
prop.type === NodeTypes.ATTRIBUTE &&
prop.name === 'ref' &&
context.scopes.vFor > 0 &&
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_FOR_REF,
context,
prop.loc
)
) {
properties.push(
createObjectProperty(
createSimpleExpression('refInFor', true),
createSimpleExpression('true', false)
)
)
}
}
let propsExpression: PropsExpression | undefined = undefined
if (mergeArgs.length) {
if (properties.length) {
mergeArgs.push(
createObjectExpression(dedupeProperties(properties), elementLoc)
)
}
if (mergeArgs.length > 1) {
propsExpression = createCallExpression(
context.helper(MERGE_PROPS),
mergeArgs,
elementLoc
)
} else {
propsExpression = mergeArgs[0]
}
} else if (properties.length) {
propsExpression = createObjectExpression(
dedupeProperties(properties),
elementLoc
)
}
if (hasDynamicKeys) {
patchFlag |= PatchFlags.FULL_PROPS
} else {
if (hasClassBinding) {
patchFlag |= PatchFlags.CLASS
}
if (hasStyleBinding) {
patchFlag |= PatchFlags.STYLE
}
if (dynamicPropNames.length) {
patchFlag |= PatchFlags.PROPS
}
if (hasHydrationEventBinding) {
patchFlag |= PatchFlags.HYDRATE_EVENTS
}
}
if (
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
) {
patchFlag |= PatchFlags.NEED_PATCH
}
return {
props: propsExpression,
directives: runtimeDirectives,
patchFlag,
dynamicPropNames
}
}