}, [
_toString(world.burn()),
ok
- ? _createVNode(\\"div\\", 0, \\"yes\\")
+ ? _createVNode(\\"div\\", null, \\"yes\\")
: \\"no\\",
_renderList(list, (value, index) => {
- return _createVNode(\\"div\\", 0, [
- _createVNode(\\"span\\", 0, _toString(value + index))
+ return _createVNode(\\"div\\", null, [
+ _createVNode(\\"span\\", null, _toString(value + index))
])
})
- ])
+ ], 2)
}
}"
`;
}, [
toString(_ctx.world.burn()),
(_ctx.ok)
- ? createVNode(\\"div\\", 0, \\"yes\\")
+ ? createVNode(\\"div\\", null, \\"yes\\")
: \\"no\\",
renderList(_ctx.list, (value, index) => {
- return createVNode(\\"div\\", 0, [
- createVNode(\\"span\\", 0, toString(value + index))
+ return createVNode(\\"div\\", null, [
+ createVNode(\\"span\\", null, toString(value + index))
])
})
- ])
+ ], 2)
}"
`;
}, [
_toString(_ctx.world.burn()),
(_ctx.ok)
- ? createVNode(\\"div\\", 0, \\"yes\\")
+ ? createVNode(\\"div\\", null, \\"yes\\")
: \\"no\\",
_renderList(_ctx.list, (value, index) => {
- return createVNode(\\"div\\", 0, [
- createVNode(\\"span\\", 0, _toString(value + index))
+ return createVNode(\\"div\\", null, [
+ createVNode(\\"span\\", null, _toString(value + index))
])
})
- ])
+ ], 2)
}"
`;
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
- return createVNode(_component_Comp, 0, {
+ return createVNode(_component_Comp, null, {
[_ctx.one]: ({ foo }) => [
toString(foo),
toString(_ctx.bar)
toString(_ctx.foo),
toString(bar)
]
- })
+ }, 256)
}"
`;
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
- return createVNode(_component_Comp, 0, {
+ return createVNode(_component_Comp, null, {
default: ({ foo }) => [
toString(foo),
toString(_ctx.bar)
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
- return createVNode(_component_Comp, 0, {
+ return createVNode(_component_Comp, null, {
default: () => [
createVNode(\\"div\\")
]
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
- return createVNode(_component_Comp, 0, {
+ return createVNode(_component_Comp, null, {
one: ({ foo }) => [
toString(foo),
toString(_ctx.bar)
const _component_Comp = resolveComponent(\\"Comp\\")
const _component_Inner = resolveComponent(\\"Inner\\")
- return createVNode(_component_Comp, 0, {
+ return createVNode(_component_Comp, null, {
default: ({ foo }) => [
- createVNode(_component_Inner, 0, {
+ createVNode(_component_Inner, null, {
default: ({ bar }) => [
toString(foo),
toString(bar),
import { transformOn } from '../../src/transforms/vOn'
import { transformStyle } from '../../src/transforms/transformStyle'
import { transformBind } from '../../src/transforms/vBind'
+import { PatchFlags } from '@vue/shared'
function parseWithElementTransform(
template: string,
expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(node.arguments).toMatchObject([
`"div"`,
- `0`,
+ `null`,
[
{
type: NodeTypes.ELEMENT,
value: _dir!.exp
}
]
- }
+ },
+ `null`,
+ String(PatchFlags.NEED_PATCH) // should generate appropriate flag
]
},
{
})
})
- test.todo('slot outlets')
+ test(`props merging: class`, () => {
+ const { node } = parseWithElementTransform(
+ `<div class="foo" :class="{ bar: isBar }" />`,
+ {
+ directiveTransforms: {
+ bind: transformBind
+ }
+ }
+ )
+ expect(node.arguments[1]).toMatchObject({
+ type: NodeTypes.JS_OBJECT_EXPRESSION,
+ properties: [
+ {
+ type: NodeTypes.JS_PROPERTY,
+ key: {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `class`,
+ isStatic: true
+ },
+ value: {
+ type: NodeTypes.JS_ARRAY_EXPRESSION,
+ elements: [
+ {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `foo`,
+ isStatic: true
+ },
+ {
+ type: NodeTypes.SIMPLE_EXPRESSION,
+ content: `{ bar: isBar }`,
+ isStatic: false
+ }
+ ]
+ }
+ }
+ ]
+ })
+ })
+
+ describe('patchFlag analysis', () => {
+ function parseWithBind(template: string) {
+ return parseWithElementTransform(template, {
+ directiveTransforms: {
+ bind: transformBind
+ }
+ })
+ }
+
+ test('CLASS', () => {
+ const { node } = parseWithBind(`<div :class="foo" />`)
+ expect(node.arguments.length).toBe(4)
+ expect(node.arguments[3]).toBe(String(PatchFlags.CLASS))
+ })
+
+ test('STYLE', () => {
+ const { node } = parseWithBind(`<div :style="foo" />`)
+ expect(node.arguments.length).toBe(4)
+ expect(node.arguments[3]).toBe(String(PatchFlags.STYLE))
+ })
+
+ test('PROPS', () => {
+ const { node } = parseWithBind(`<div id="foo" :foo="bar" :baz="qux" />`)
+ expect(node.arguments.length).toBe(5)
+ expect(node.arguments[3]).toBe(String(PatchFlags.PROPS))
+ expect(node.arguments[4]).toBe(`["foo", "baz"]`)
+ })
+
+ test('CLASS + STYLE + PROPS', () => {
+ const { node } = parseWithBind(
+ `<div id="foo" :class="cls" :style="styl" :foo="bar" :baz="qux"/>`
+ )
+ expect(node.arguments.length).toBe(5)
+ expect(node.arguments[3]).toBe(
+ String(PatchFlags.PROPS | PatchFlags.CLASS | PatchFlags.STYLE)
+ )
+ expect(node.arguments[4]).toBe(`["foo", "baz"]`)
+ })
+
+ test('FULL_PROPS (v-bind)', () => {
+ const { node } = parseWithBind(`<div v-bind="foo" />`)
+ expect(node.arguments.length).toBe(4)
+ expect(node.arguments[3]).toBe(String(PatchFlags.FULL_PROPS))
+ })
+
+ test('FULL_PROPS (dynamic key)', () => {
+ const { node } = parseWithBind(`<div :[foo]="bar" />`)
+ expect(node.arguments.length).toBe(4)
+ expect(node.arguments[3]).toBe(String(PatchFlags.FULL_PROPS))
+ })
+
+ test('FULL_PROPS (w/ others)', () => {
+ const { node } = parseWithBind(
+ `<div id="foo" v-bind="bar" :class="cls" />`
+ )
+ expect(node.arguments.length).toBe(4)
+ expect(node.arguments[3]).toBe(String(PatchFlags.FULL_PROPS))
+ })
+
+ test('NEED_PATCH (static ref)', () => {
+ const { node } = parseWithBind(`<div ref="foo" />`)
+ expect(node.arguments.length).toBe(4)
+ expect(node.arguments[3]).toBe(String(PatchFlags.NEED_PATCH))
+ })
+
+ test('NEED_PATCH (dynamic ref)', () => {
+ const { node } = parseWithBind(`<div :ref="foo" />`)
+ expect(node.arguments.length).toBe(4)
+ expect(node.arguments[3]).toBe(String(PatchFlags.NEED_PATCH))
+ })
+
+ test('NEED_PATCH (custom directives)', () => {
+ const { node } = parseWithBind(`<div v-foo />`)
+ const vnodeCall = node.arguments[0] as CallExpression
+ expect(vnodeCall.arguments.length).toBe(4)
+ expect(vnodeCall.arguments[3]).toBe(String(PatchFlags.NEED_PATCH))
+ })
+ })
})
type: NodeTypes.JS_CALL_EXPRESSION,
arguments: [
`_component_Inner`,
- `0`,
+ `null`,
createSlotMatcher({
default: {
type: NodeTypes.JS_SLOT_FUNCTION,
export interface CallExpression extends Node {
type: NodeTypes.JS_CALL_EXPRESSION
- callee: string | ExpressionNode
+ callee: string
arguments: (string | JSChildNode | ChildNode[])[]
}
}
export function createCallExpression(
- callee: string | ExpressionNode,
+ callee: string,
args: CallExpression['arguments'],
loc: SourceLocation
): CallExpression {
context: CodegenContext,
multilines = node.arguments.length > 2
) {
- if (isString(node.callee)) {
- context.push(node.callee + `(`, node, true)
- } else {
- genNode(node.callee, context)
- context.push(`(`)
- }
+ context.push(node.callee + `(`, node, true)
multilines && context.indent()
genNodeList(node.arguments, context, multilines)
multilines && context.deindent()
Property,
ExpressionNode,
createSimpleExpression,
- JSChildNode
+ JSChildNode,
+ SimpleExpressionNode
} from './ast'
import { isString, isArray } from '@vue/shared'
import { CompilerError, defaultOnError } from './errors'
onNodeRemoved: () => void
addIdentifiers(exp: ExpressionNode): void
removeIdentifiers(exp: ExpressionNode): void
- hoist(exp: JSChildNode): ExpressionNode
+ hoist(exp: JSChildNode): SimpleExpressionNode
}
function createTransformContext(
Property,
SourceLocation
} from '../ast'
-import { isArray } from '@vue/shared'
+import { isArray, PatchFlags } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import {
CREATE_VNODE,
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) {
]
// 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
): {
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(
// 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(
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.
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
}
}
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 }"/>
)
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(
continue
}
seenSlotNames.add(name)
+ } else {
+ hasDynamicSlotName = true
}
slots.push(
buildSlot(slotName || `default`, slotProps, children, nodeLoc)
slots.push(buildSlot(`default`, undefined, children, loc))
}
- return createObjectExpression(slots, loc)
+ return {
+ slots: createObjectExpression(slots, loc),
+ hasDynamicSlotName
+ }
}
function buildSlot(
it('patch fragment children (compiler generated, unkeyed)', () => {
const root = nodeOps.createElement('div')
render(
- createVNode(Fragment, 0, [h('div', 'one'), 'two'], PatchFlags.UNKEYED),
+ createVNode(Fragment, null, [h('div', 'one'), 'two'], PatchFlags.UNKEYED),
root
)
expect(serializeInner(root)).toBe(`<!----><div>one</div>two<!---->`)
render(
createVNode(
Fragment,
- 0,
+ null,
[h('div', 'foo'), 'bar', 'baz'],
PatchFlags.UNKEYED
),
render(
createVNode(
Fragment,
- 0,
+ null,
[h('div', { key: 1 }, 'one'), h('div', { key: 2 }, 'two')],
PatchFlags.KEYED
),
render(
createVNode(
Fragment,
- 0,
+ null,
[h('div', { key: 2 }, 'two'), h('div', { key: 1 }, 'one')],
PatchFlags.KEYED
),
isObject,
isReservedProp,
hasOwn,
- toTypeString
+ toTypeString,
+ PatchFlags
} from '@vue/shared'
import { warn } from './warning'
import { Data, ComponentInternalInstance } from './component'
-import { PatchFlags } from './patchFlags'
export type ComponentPropsOptions<P = Data> = {
[K in keyof P]: Prop<P[K]> | null
import { VNode, normalizeVNode, createVNode, Comment } from './vnode'
import { ShapeFlags } from './shapeFlags'
import { handleError, ErrorCodes } from './errorHandling'
-import { PatchFlags } from './patchFlags'
+import { PatchFlags } from '@vue/shared'
// mark the current rendering instance for asset resolution (e.g.
// resolveComponent, resolveDirective) during render
EMPTY_ARR,
isReservedProp,
isFunction,
- isArray
+ isArray,
+ PatchFlags
} from '@vue/shared'
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
import {
} from '@vue/reactivity'
import { resolveProps } from './componentProps'
import { resolveSlots } from './componentSlots'
-import { PatchFlags } from './patchFlags'
import { ShapeFlags } from './shapeFlags'
import { pushWarningContext, popWarningContext, warn } from './warning'
import { invokeDirectiveHook } from './directives'
// VNode type symbols
export { Text, Comment, Fragment, Portal, Suspense } from './vnode'
// VNode flags
-export { PublicPatchFlags as PatchFlags } from './patchFlags'
export { PublicShapeFlags as ShapeFlags } from './shapeFlags'
+export { PublicPatchFlags as PatchFlags } from '@vue/shared'
// For advanced plugins
export { getCurrentInstance } from './component'
isString,
isObject,
EMPTY_ARR,
- extend
+ extend,
+ PatchFlags
} from '@vue/shared'
import { ComponentInternalInstance, Data, SetupProxySymbol } from './component'
import { RawSlots } from './componentSlots'
-import { PatchFlags } from './patchFlags'
import { ShapeFlags } from './shapeFlags'
import { isReactive } from '@vue/reactivity'
import { AppContext } from './apiApp'
export function createVNode(
type: VNodeTypes,
- props: { [key: string]: any } | null | 0 = null,
- children: any = null,
+ props: { [key: string]: any } | null = null,
+ children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null
): VNode {
- // Allow passing 0 for props, this can save bytes on generated code.
- props = props || null
-
// class & style normalization.
if (props !== null) {
// for reactive or proxy objects, we need to clone it to enable mutation.
+export * from './patchFlags'
+
export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
? Object.freeze({})
: {}
// Indicates an element with dynamic textContent (children fast path)
TEXT = 1,
- // Indicates an element with dynamic class.
- // The compiler also pre-normalizes the :class binding:
- // - b -> normalize(b)
- // - ['foo', b] -> 'foo' + normalize(b)
- // - { a, b: c } -> (a ? a : '') + (b ? c : '')
- // - ['a', b, { c }] -> 'a' + normalize(b) + (c ? c : '')
+ // Indicates an element with dynamic class binding.
CLASS = 1 << 1,
// Indicates an element with dynamic style
// Indicates an element that only needs non-props patching, e.g. ref or
// directives (vnodeXXX hooks). It simply marks the vnode as "need patch",
// since every pathced vnode checks for refs and vnodeXXX hooks.
+ // This flag is never directly matched against, it simply serves as a non-zero
+ // value.
NEED_PATCH = 1 << 5,
// Indicates a fragment or element with keyed or partially-keyed v-for