feat(compiler): generate patchFlags for runtime

This commit is contained in:
Evan You
2019-09-30 21:17:12 -04:00
parent da0d785d84
commit d67418002f
19 changed files with 267 additions and 70 deletions

View File

@@ -1 +0,0 @@
// TODO

View File

@@ -16,7 +16,7 @@ import {
Property,
SourceLocation
} from '../ast'
import { isArray } from '@vue/shared'
import { isArray, PatchFlags } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import {
CREATE_VNODE,
@@ -41,7 +41,9 @@ export const transformElement: NodeTransform = (node, context) => {
const isComponent = node.tagType === ElementTypes.COMPONENT
let hasProps = node.props.length > 0
const hasChildren = node.children.length > 0
let patchFlag: number = 0
let runtimeDirectives: DirectiveNode[] | undefined
let dynamicPropNames: string[] | undefined
let componentIdentifier: string | undefined
if (isComponent) {
@@ -58,26 +60,51 @@ export const transformElement: NodeTransform = (node, context) => {
]
// props
if (hasProps) {
const { props, directives } = buildProps(
const propsBuildResult = buildProps(
node.props,
node.loc,
context,
isComponent
)
runtimeDirectives = directives
if (!props) {
patchFlag = propsBuildResult.patchFlag
dynamicPropNames = propsBuildResult.dynamicPropNames
runtimeDirectives = propsBuildResult.directives
if (!propsBuildResult.props) {
hasProps = false
} else {
args.push(props)
args.push(propsBuildResult.props)
}
}
// children
if (hasChildren) {
if (!hasProps) {
// placeholder for null props, but use `0` for more condense code
args.push(`0`)
args.push(`null`)
}
if (isComponent) {
const { slots, hasDynamicSlotName } = buildSlots(node, context)
args.push(slots)
if (hasDynamicSlotName) {
patchFlag |= PatchFlags.DYNAMIC_SLOTS
}
} else {
// only v-for fragments will have keyed/unkeyed flags
args.push(node.children)
}
}
// patchFlag & dynamicPropNames
if (patchFlag !== 0) {
if (!hasChildren) {
if (!hasProps) {
args.push(`null`)
}
args.push(`null`)
}
args.push(String(patchFlag))
if (dynamicPropNames && dynamicPropNames.length) {
args.push(
`[${dynamicPropNames.map(n => JSON.stringify(n)).join(`, `)}]`
)
}
args.push(isComponent ? buildSlots(node, context) : node.children)
}
const { loc } = node
@@ -118,17 +145,30 @@ export function buildProps(
): {
props: PropsExpression | undefined
directives: DirectiveNode[]
patchFlag: number
dynamicPropNames: string[]
} {
let isStatic = true
let properties: ObjectExpression['properties'] = []
const mergeArgs: PropsExpression[] = []
const runtimeDirectives: DirectiveNode[] = []
// patchFlag analysis
let patchFlag = 0
const dynamicPropNames: string[] = []
let hasDynammicKeys = false
let hasClassBinding = false
let hasStyleBinding = false
let hasRef = false
for (let i = 0; i < props.length; i++) {
// static attribute
const prop = props[i]
if (prop.type === NodeTypes.ATTRIBUTE) {
const { loc, name, value } = prop
if (name === 'ref') {
hasRef = true
}
properties.push(
createObjectProperty(
createSimpleExpression(
@@ -162,6 +202,7 @@ export function buildProps(
// special case for v-bind and v-on with no argument
const isBind = name === 'bind'
if (!arg && (isBind || name === 'on')) {
hasDynammicKeys = true
if (exp) {
if (properties.length) {
mergeArgs.push(
@@ -193,6 +234,24 @@ export function buildProps(
continue
}
// patchFlag analysis
if (isBind && arg) {
if (arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic) {
const name = arg.content
if (name === 'ref') {
hasRef = true
} else if (name === 'class') {
hasClassBinding = true
} else if (name === 'style') {
hasStyleBinding = true
} else {
dynamicPropNames.push(name)
}
} else {
hasDynammicKeys = true
}
}
const directiveTransform = context.directiveTransforms[name]
if (directiveTransform) {
// has built-in directive transform.
@@ -243,9 +302,29 @@ export function buildProps(
propsExpression = context.hoist(propsExpression)
}
// determine the flags to add
if (hasDynammicKeys) {
patchFlag |= PatchFlags.FULL_PROPS
} else {
if (hasClassBinding) {
patchFlag |= PatchFlags.CLASS
}
if (hasStyleBinding) {
patchFlag |= PatchFlags.STYLE
}
if (dynamicPropNames.length) {
patchFlag |= PatchFlags.PROPS
}
}
if (patchFlag === 0 && (hasRef || runtimeDirectives.length > 0)) {
patchFlag |= PatchFlags.NEED_PATCH
}
return {
props: propsExpression,
directives: runtimeDirectives
directives: runtimeDirectives,
patchFlag,
dynamicPropNames
}
}

View File

@@ -44,8 +44,12 @@ export const trackSlotScopes: NodeTransform = (node, context) => {
export function buildSlots(
{ props, children, loc }: ElementNode,
context: TransformContext
): ObjectExpression {
): {
slots: ObjectExpression
hasDynamicSlotName: boolean
} {
const slots: Property[] = []
let hasDynamicSlotName = false
// 1. Check for default slot with slotProps on component itself.
// <Comp v-slot="{ prop }"/>
@@ -83,11 +87,11 @@ export function buildSlots(
)
break
} else {
// check duplicate slot names
if (
!slotName ||
(slotName.type === NodeTypes.SIMPLE_EXPRESSION && slotName.isStatic)
) {
// check duplicate slot names
const name = slotName ? slotName.content : `default`
if (seenSlotNames.has(name)) {
context.onError(
@@ -96,6 +100,8 @@ export function buildSlots(
continue
}
seenSlotNames.add(name)
} else {
hasDynamicSlotName = true
}
slots.push(
buildSlot(slotName || `default`, slotProps, children, nodeLoc)
@@ -120,7 +126,10 @@ export function buildSlots(
slots.push(buildSlot(`default`, undefined, children, loc))
}
return createObjectExpression(slots, loc)
return {
slots: createObjectExpression(slots, loc),
hasDynamicSlotName
}
}
function buildSlot(