feat(compiler): generate patchFlags for runtime
This commit is contained in:
parent
da0d785d84
commit
d67418002f
@ -11,14 +11,14 @@ return function render() {
|
|||||||
}, [
|
}, [
|
||||||
_toString(world.burn()),
|
_toString(world.burn()),
|
||||||
ok
|
ok
|
||||||
? _createVNode(\\"div\\", 0, \\"yes\\")
|
? _createVNode(\\"div\\", null, \\"yes\\")
|
||||||
: \\"no\\",
|
: \\"no\\",
|
||||||
_renderList(list, (value, index) => {
|
_renderList(list, (value, index) => {
|
||||||
return _createVNode(\\"div\\", 0, [
|
return _createVNode(\\"div\\", null, [
|
||||||
_createVNode(\\"span\\", 0, _toString(value + index))
|
_createVNode(\\"span\\", null, _toString(value + index))
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
])
|
], 2)
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@ -34,14 +34,14 @@ return function render() {
|
|||||||
}, [
|
}, [
|
||||||
toString(_ctx.world.burn()),
|
toString(_ctx.world.burn()),
|
||||||
(_ctx.ok)
|
(_ctx.ok)
|
||||||
? createVNode(\\"div\\", 0, \\"yes\\")
|
? createVNode(\\"div\\", null, \\"yes\\")
|
||||||
: \\"no\\",
|
: \\"no\\",
|
||||||
renderList(_ctx.list, (value, index) => {
|
renderList(_ctx.list, (value, index) => {
|
||||||
return createVNode(\\"div\\", 0, [
|
return createVNode(\\"div\\", null, [
|
||||||
createVNode(\\"span\\", 0, toString(value + index))
|
createVNode(\\"span\\", null, toString(value + index))
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
])
|
], 2)
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -56,13 +56,13 @@ export default function render() {
|
|||||||
}, [
|
}, [
|
||||||
_toString(_ctx.world.burn()),
|
_toString(_ctx.world.burn()),
|
||||||
(_ctx.ok)
|
(_ctx.ok)
|
||||||
? createVNode(\\"div\\", 0, \\"yes\\")
|
? createVNode(\\"div\\", null, \\"yes\\")
|
||||||
: \\"no\\",
|
: \\"no\\",
|
||||||
_renderList(_ctx.list, (value, index) => {
|
_renderList(_ctx.list, (value, index) => {
|
||||||
return createVNode(\\"div\\", 0, [
|
return createVNode(\\"div\\", null, [
|
||||||
createVNode(\\"span\\", 0, _toString(value + index))
|
createVNode(\\"span\\", null, _toString(value + index))
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
])
|
], 2)
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -7,7 +7,7 @@ return function render() {
|
|||||||
const _ctx = this
|
const _ctx = this
|
||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
const _component_Comp = resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
return createVNode(_component_Comp, 0, {
|
return createVNode(_component_Comp, null, {
|
||||||
[_ctx.one]: ({ foo }) => [
|
[_ctx.one]: ({ foo }) => [
|
||||||
toString(foo),
|
toString(foo),
|
||||||
toString(_ctx.bar)
|
toString(_ctx.bar)
|
||||||
@ -16,7 +16,7 @@ return function render() {
|
|||||||
toString(_ctx.foo),
|
toString(_ctx.foo),
|
||||||
toString(bar)
|
toString(bar)
|
||||||
]
|
]
|
||||||
})
|
}, 256)
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ return function render() {
|
|||||||
const _ctx = this
|
const _ctx = this
|
||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
const _component_Comp = resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
return createVNode(_component_Comp, 0, {
|
return createVNode(_component_Comp, null, {
|
||||||
default: ({ foo }) => [
|
default: ({ foo }) => [
|
||||||
toString(foo),
|
toString(foo),
|
||||||
toString(_ctx.bar)
|
toString(_ctx.bar)
|
||||||
@ -43,7 +43,7 @@ return function render() {
|
|||||||
const _ctx = this
|
const _ctx = this
|
||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
const _component_Comp = resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
return createVNode(_component_Comp, 0, {
|
return createVNode(_component_Comp, null, {
|
||||||
default: () => [
|
default: () => [
|
||||||
createVNode(\\"div\\")
|
createVNode(\\"div\\")
|
||||||
]
|
]
|
||||||
@ -58,7 +58,7 @@ return function render() {
|
|||||||
const _ctx = this
|
const _ctx = this
|
||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
const _component_Comp = resolveComponent(\\"Comp\\")
|
||||||
|
|
||||||
return createVNode(_component_Comp, 0, {
|
return createVNode(_component_Comp, null, {
|
||||||
one: ({ foo }) => [
|
one: ({ foo }) => [
|
||||||
toString(foo),
|
toString(foo),
|
||||||
toString(_ctx.bar)
|
toString(_ctx.bar)
|
||||||
@ -79,9 +79,9 @@ return function render() {
|
|||||||
const _component_Comp = resolveComponent(\\"Comp\\")
|
const _component_Comp = resolveComponent(\\"Comp\\")
|
||||||
const _component_Inner = resolveComponent(\\"Inner\\")
|
const _component_Inner = resolveComponent(\\"Inner\\")
|
||||||
|
|
||||||
return createVNode(_component_Comp, 0, {
|
return createVNode(_component_Comp, null, {
|
||||||
default: ({ foo }) => [
|
default: ({ foo }) => [
|
||||||
createVNode(_component_Inner, 0, {
|
createVNode(_component_Inner, null, {
|
||||||
default: ({ bar }) => [
|
default: ({ bar }) => [
|
||||||
toString(foo),
|
toString(foo),
|
||||||
toString(bar),
|
toString(bar),
|
||||||
|
@ -24,6 +24,7 @@ import { transformElement } from '../../src/transforms/transformElement'
|
|||||||
import { transformOn } from '../../src/transforms/vOn'
|
import { transformOn } from '../../src/transforms/vOn'
|
||||||
import { transformStyle } from '../../src/transforms/transformStyle'
|
import { transformStyle } from '../../src/transforms/transformStyle'
|
||||||
import { transformBind } from '../../src/transforms/vBind'
|
import { transformBind } from '../../src/transforms/vBind'
|
||||||
|
import { PatchFlags } from '@vue/shared'
|
||||||
|
|
||||||
function parseWithElementTransform(
|
function parseWithElementTransform(
|
||||||
template: string,
|
template: string,
|
||||||
@ -127,7 +128,7 @@ describe('compiler: element transform', () => {
|
|||||||
expect(node.callee).toBe(`_${CREATE_VNODE}`)
|
expect(node.callee).toBe(`_${CREATE_VNODE}`)
|
||||||
expect(node.arguments).toMatchObject([
|
expect(node.arguments).toMatchObject([
|
||||||
`"div"`,
|
`"div"`,
|
||||||
`0`,
|
`null`,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: NodeTypes.ELEMENT,
|
type: NodeTypes.ELEMENT,
|
||||||
@ -351,7 +352,9 @@ describe('compiler: element transform', () => {
|
|||||||
value: _dir!.exp
|
value: _dir!.exp
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
`null`,
|
||||||
|
String(PatchFlags.NEED_PATCH) // should generate appropriate flag
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -546,5 +549,121 @@ describe('compiler: element transform', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
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))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -260,7 +260,7 @@ describe('compiler: transform component slots', () => {
|
|||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
arguments: [
|
arguments: [
|
||||||
`_component_Inner`,
|
`_component_Inner`,
|
||||||
`0`,
|
`null`,
|
||||||
createSlotMatcher({
|
createSlotMatcher({
|
||||||
default: {
|
default: {
|
||||||
type: NodeTypes.JS_SLOT_FUNCTION,
|
type: NodeTypes.JS_SLOT_FUNCTION,
|
||||||
|
@ -169,7 +169,7 @@ export type JSChildNode =
|
|||||||
|
|
||||||
export interface CallExpression extends Node {
|
export interface CallExpression extends Node {
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION
|
type: NodeTypes.JS_CALL_EXPRESSION
|
||||||
callee: string | ExpressionNode
|
callee: string
|
||||||
arguments: (string | JSChildNode | ChildNode[])[]
|
arguments: (string | JSChildNode | ChildNode[])[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +268,7 @@ export function createCompoundExpression(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createCallExpression(
|
export function createCallExpression(
|
||||||
callee: string | ExpressionNode,
|
callee: string,
|
||||||
args: CallExpression['arguments'],
|
args: CallExpression['arguments'],
|
||||||
loc: SourceLocation
|
loc: SourceLocation
|
||||||
): CallExpression {
|
): CallExpression {
|
||||||
|
@ -544,12 +544,7 @@ function genCallExpression(
|
|||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
multilines = node.arguments.length > 2
|
multilines = node.arguments.length > 2
|
||||||
) {
|
) {
|
||||||
if (isString(node.callee)) {
|
context.push(node.callee + `(`, node, true)
|
||||||
context.push(node.callee + `(`, node, true)
|
|
||||||
} else {
|
|
||||||
genNode(node.callee, context)
|
|
||||||
context.push(`(`)
|
|
||||||
}
|
|
||||||
multilines && context.indent()
|
multilines && context.indent()
|
||||||
genNodeList(node.arguments, context, multilines)
|
genNodeList(node.arguments, context, multilines)
|
||||||
multilines && context.deindent()
|
multilines && context.deindent()
|
||||||
|
@ -8,7 +8,8 @@ import {
|
|||||||
Property,
|
Property,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
JSChildNode
|
JSChildNode,
|
||||||
|
SimpleExpressionNode
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { isString, isArray } from '@vue/shared'
|
import { isString, isArray } from '@vue/shared'
|
||||||
import { CompilerError, defaultOnError } from './errors'
|
import { CompilerError, defaultOnError } from './errors'
|
||||||
@ -65,7 +66,7 @@ export interface TransformContext extends Required<TransformOptions> {
|
|||||||
onNodeRemoved: () => void
|
onNodeRemoved: () => void
|
||||||
addIdentifiers(exp: ExpressionNode): void
|
addIdentifiers(exp: ExpressionNode): void
|
||||||
removeIdentifiers(exp: ExpressionNode): void
|
removeIdentifiers(exp: ExpressionNode): void
|
||||||
hoist(exp: JSChildNode): ExpressionNode
|
hoist(exp: JSChildNode): SimpleExpressionNode
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTransformContext(
|
function createTransformContext(
|
||||||
|
@ -1 +0,0 @@
|
|||||||
// TODO
|
|
@ -1 +0,0 @@
|
|||||||
// TODO
|
|
@ -16,7 +16,7 @@ import {
|
|||||||
Property,
|
Property,
|
||||||
SourceLocation
|
SourceLocation
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { isArray } from '@vue/shared'
|
import { isArray, PatchFlags } from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import {
|
import {
|
||||||
CREATE_VNODE,
|
CREATE_VNODE,
|
||||||
@ -41,7 +41,9 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
const isComponent = node.tagType === ElementTypes.COMPONENT
|
const isComponent = node.tagType === ElementTypes.COMPONENT
|
||||||
let hasProps = node.props.length > 0
|
let hasProps = node.props.length > 0
|
||||||
const hasChildren = node.children.length > 0
|
const hasChildren = node.children.length > 0
|
||||||
|
let patchFlag: number = 0
|
||||||
let runtimeDirectives: DirectiveNode[] | undefined
|
let runtimeDirectives: DirectiveNode[] | undefined
|
||||||
|
let dynamicPropNames: string[] | undefined
|
||||||
let componentIdentifier: string | undefined
|
let componentIdentifier: string | undefined
|
||||||
|
|
||||||
if (isComponent) {
|
if (isComponent) {
|
||||||
@ -58,26 +60,51 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
]
|
]
|
||||||
// props
|
// props
|
||||||
if (hasProps) {
|
if (hasProps) {
|
||||||
const { props, directives } = buildProps(
|
const propsBuildResult = buildProps(
|
||||||
node.props,
|
node.props,
|
||||||
node.loc,
|
node.loc,
|
||||||
context,
|
context,
|
||||||
isComponent
|
isComponent
|
||||||
)
|
)
|
||||||
runtimeDirectives = directives
|
patchFlag = propsBuildResult.patchFlag
|
||||||
if (!props) {
|
dynamicPropNames = propsBuildResult.dynamicPropNames
|
||||||
|
runtimeDirectives = propsBuildResult.directives
|
||||||
|
if (!propsBuildResult.props) {
|
||||||
hasProps = false
|
hasProps = false
|
||||||
} else {
|
} else {
|
||||||
args.push(props)
|
args.push(propsBuildResult.props)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// children
|
// children
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
if (!hasProps) {
|
if (!hasProps) {
|
||||||
// placeholder for null props, but use `0` for more condense code
|
args.push(`null`)
|
||||||
args.push(`0`)
|
}
|
||||||
|
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
|
const { loc } = node
|
||||||
@ -118,17 +145,30 @@ export function buildProps(
|
|||||||
): {
|
): {
|
||||||
props: PropsExpression | undefined
|
props: PropsExpression | undefined
|
||||||
directives: DirectiveNode[]
|
directives: DirectiveNode[]
|
||||||
|
patchFlag: number
|
||||||
|
dynamicPropNames: string[]
|
||||||
} {
|
} {
|
||||||
let isStatic = true
|
let isStatic = true
|
||||||
let properties: ObjectExpression['properties'] = []
|
let properties: ObjectExpression['properties'] = []
|
||||||
const mergeArgs: PropsExpression[] = []
|
const mergeArgs: PropsExpression[] = []
|
||||||
const runtimeDirectives: DirectiveNode[] = []
|
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++) {
|
for (let i = 0; i < props.length; i++) {
|
||||||
// static attribute
|
// static attribute
|
||||||
const prop = props[i]
|
const prop = props[i]
|
||||||
if (prop.type === NodeTypes.ATTRIBUTE) {
|
if (prop.type === NodeTypes.ATTRIBUTE) {
|
||||||
const { loc, name, value } = prop
|
const { loc, name, value } = prop
|
||||||
|
if (name === 'ref') {
|
||||||
|
hasRef = true
|
||||||
|
}
|
||||||
properties.push(
|
properties.push(
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
createSimpleExpression(
|
createSimpleExpression(
|
||||||
@ -162,6 +202,7 @@ export function buildProps(
|
|||||||
// special case for v-bind and v-on with no argument
|
// special case for v-bind and v-on with no argument
|
||||||
const isBind = name === 'bind'
|
const isBind = name === 'bind'
|
||||||
if (!arg && (isBind || name === 'on')) {
|
if (!arg && (isBind || name === 'on')) {
|
||||||
|
hasDynammicKeys = true
|
||||||
if (exp) {
|
if (exp) {
|
||||||
if (properties.length) {
|
if (properties.length) {
|
||||||
mergeArgs.push(
|
mergeArgs.push(
|
||||||
@ -193,6 +234,24 @@ export function buildProps(
|
|||||||
continue
|
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]
|
const directiveTransform = context.directiveTransforms[name]
|
||||||
if (directiveTransform) {
|
if (directiveTransform) {
|
||||||
// has built-in directive transform.
|
// has built-in directive transform.
|
||||||
@ -243,9 +302,29 @@ export function buildProps(
|
|||||||
propsExpression = context.hoist(propsExpression)
|
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 {
|
return {
|
||||||
props: propsExpression,
|
props: propsExpression,
|
||||||
directives: runtimeDirectives
|
directives: runtimeDirectives,
|
||||||
|
patchFlag,
|
||||||
|
dynamicPropNames
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,8 +44,12 @@ export const trackSlotScopes: NodeTransform = (node, context) => {
|
|||||||
export function buildSlots(
|
export function buildSlots(
|
||||||
{ props, children, loc }: ElementNode,
|
{ props, children, loc }: ElementNode,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
): ObjectExpression {
|
): {
|
||||||
|
slots: ObjectExpression
|
||||||
|
hasDynamicSlotName: boolean
|
||||||
|
} {
|
||||||
const slots: Property[] = []
|
const slots: Property[] = []
|
||||||
|
let hasDynamicSlotName = false
|
||||||
|
|
||||||
// 1. Check for default slot with slotProps on component itself.
|
// 1. Check for default slot with slotProps on component itself.
|
||||||
// <Comp v-slot="{ prop }"/>
|
// <Comp v-slot="{ prop }"/>
|
||||||
@ -83,11 +87,11 @@ export function buildSlots(
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
// check duplicate slot names
|
|
||||||
if (
|
if (
|
||||||
!slotName ||
|
!slotName ||
|
||||||
(slotName.type === NodeTypes.SIMPLE_EXPRESSION && slotName.isStatic)
|
(slotName.type === NodeTypes.SIMPLE_EXPRESSION && slotName.isStatic)
|
||||||
) {
|
) {
|
||||||
|
// check duplicate slot names
|
||||||
const name = slotName ? slotName.content : `default`
|
const name = slotName ? slotName.content : `default`
|
||||||
if (seenSlotNames.has(name)) {
|
if (seenSlotNames.has(name)) {
|
||||||
context.onError(
|
context.onError(
|
||||||
@ -96,6 +100,8 @@ export function buildSlots(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenSlotNames.add(name)
|
seenSlotNames.add(name)
|
||||||
|
} else {
|
||||||
|
hasDynamicSlotName = true
|
||||||
}
|
}
|
||||||
slots.push(
|
slots.push(
|
||||||
buildSlot(slotName || `default`, slotProps, children, nodeLoc)
|
buildSlot(slotName || `default`, slotProps, children, nodeLoc)
|
||||||
@ -120,7 +126,10 @@ export function buildSlots(
|
|||||||
slots.push(buildSlot(`default`, undefined, children, loc))
|
slots.push(buildSlot(`default`, undefined, children, loc))
|
||||||
}
|
}
|
||||||
|
|
||||||
return createObjectExpression(slots, loc)
|
return {
|
||||||
|
slots: createObjectExpression(slots, loc),
|
||||||
|
hasDynamicSlotName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSlot(
|
function buildSlot(
|
||||||
|
@ -107,7 +107,7 @@ describe('renderer: fragment', () => {
|
|||||||
it('patch fragment children (compiler generated, unkeyed)', () => {
|
it('patch fragment children (compiler generated, unkeyed)', () => {
|
||||||
const root = nodeOps.createElement('div')
|
const root = nodeOps.createElement('div')
|
||||||
render(
|
render(
|
||||||
createVNode(Fragment, 0, [h('div', 'one'), 'two'], PatchFlags.UNKEYED),
|
createVNode(Fragment, null, [h('div', 'one'), 'two'], PatchFlags.UNKEYED),
|
||||||
root
|
root
|
||||||
)
|
)
|
||||||
expect(serializeInner(root)).toBe(`<!----><div>one</div>two<!---->`)
|
expect(serializeInner(root)).toBe(`<!----><div>one</div>two<!---->`)
|
||||||
@ -115,7 +115,7 @@ describe('renderer: fragment', () => {
|
|||||||
render(
|
render(
|
||||||
createVNode(
|
createVNode(
|
||||||
Fragment,
|
Fragment,
|
||||||
0,
|
null,
|
||||||
[h('div', 'foo'), 'bar', 'baz'],
|
[h('div', 'foo'), 'bar', 'baz'],
|
||||||
PatchFlags.UNKEYED
|
PatchFlags.UNKEYED
|
||||||
),
|
),
|
||||||
@ -130,7 +130,7 @@ describe('renderer: fragment', () => {
|
|||||||
render(
|
render(
|
||||||
createVNode(
|
createVNode(
|
||||||
Fragment,
|
Fragment,
|
||||||
0,
|
null,
|
||||||
[h('div', { key: 1 }, 'one'), h('div', { key: 2 }, 'two')],
|
[h('div', { key: 1 }, 'one'), h('div', { key: 2 }, 'two')],
|
||||||
PatchFlags.KEYED
|
PatchFlags.KEYED
|
||||||
),
|
),
|
||||||
@ -144,7 +144,7 @@ describe('renderer: fragment', () => {
|
|||||||
render(
|
render(
|
||||||
createVNode(
|
createVNode(
|
||||||
Fragment,
|
Fragment,
|
||||||
0,
|
null,
|
||||||
[h('div', { key: 2 }, 'two'), h('div', { key: 1 }, 'one')],
|
[h('div', { key: 2 }, 'two'), h('div', { key: 1 }, 'one')],
|
||||||
PatchFlags.KEYED
|
PatchFlags.KEYED
|
||||||
),
|
),
|
||||||
|
@ -10,11 +10,11 @@ import {
|
|||||||
isObject,
|
isObject,
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
hasOwn,
|
hasOwn,
|
||||||
toTypeString
|
toTypeString,
|
||||||
|
PatchFlags
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { Data, ComponentInternalInstance } from './component'
|
import { Data, ComponentInternalInstance } from './component'
|
||||||
import { PatchFlags } from './patchFlags'
|
|
||||||
|
|
||||||
export type ComponentPropsOptions<P = Data> = {
|
export type ComponentPropsOptions<P = Data> = {
|
||||||
[K in keyof P]: Prop<P[K]> | null
|
[K in keyof P]: Prop<P[K]> | null
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
import { VNode, normalizeVNode, createVNode, Comment } from './vnode'
|
import { VNode, normalizeVNode, createVNode, Comment } from './vnode'
|
||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
import { handleError, ErrorCodes } from './errorHandling'
|
import { handleError, ErrorCodes } from './errorHandling'
|
||||||
import { PatchFlags } from './patchFlags'
|
import { PatchFlags } from '@vue/shared'
|
||||||
|
|
||||||
// mark the current rendering instance for asset resolution (e.g.
|
// mark the current rendering instance for asset resolution (e.g.
|
||||||
// resolveComponent, resolveDirective) during render
|
// resolveComponent, resolveDirective) during render
|
||||||
|
@ -25,7 +25,8 @@ import {
|
|||||||
EMPTY_ARR,
|
EMPTY_ARR,
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
isFunction,
|
isFunction,
|
||||||
isArray
|
isArray,
|
||||||
|
PatchFlags
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
||||||
import {
|
import {
|
||||||
@ -38,7 +39,6 @@ import {
|
|||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
import { resolveProps } from './componentProps'
|
import { resolveProps } from './componentProps'
|
||||||
import { resolveSlots } from './componentSlots'
|
import { resolveSlots } from './componentSlots'
|
||||||
import { PatchFlags } from './patchFlags'
|
|
||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
import { pushWarningContext, popWarningContext, warn } from './warning'
|
import { pushWarningContext, popWarningContext, warn } from './warning'
|
||||||
import { invokeDirectiveHook } from './directives'
|
import { invokeDirectiveHook } from './directives'
|
||||||
|
@ -21,8 +21,8 @@ export {
|
|||||||
// VNode type symbols
|
// VNode type symbols
|
||||||
export { Text, Comment, Fragment, Portal, Suspense } from './vnode'
|
export { Text, Comment, Fragment, Portal, Suspense } from './vnode'
|
||||||
// VNode flags
|
// VNode flags
|
||||||
export { PublicPatchFlags as PatchFlags } from './patchFlags'
|
|
||||||
export { PublicShapeFlags as ShapeFlags } from './shapeFlags'
|
export { PublicShapeFlags as ShapeFlags } from './shapeFlags'
|
||||||
|
export { PublicPatchFlags as PatchFlags } from '@vue/shared'
|
||||||
|
|
||||||
// For advanced plugins
|
// For advanced plugins
|
||||||
export { getCurrentInstance } from './component'
|
export { getCurrentInstance } from './component'
|
||||||
|
@ -4,11 +4,11 @@ import {
|
|||||||
isString,
|
isString,
|
||||||
isObject,
|
isObject,
|
||||||
EMPTY_ARR,
|
EMPTY_ARR,
|
||||||
extend
|
extend,
|
||||||
|
PatchFlags
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { ComponentInternalInstance, Data, SetupProxySymbol } from './component'
|
import { ComponentInternalInstance, Data, SetupProxySymbol } from './component'
|
||||||
import { RawSlots } from './componentSlots'
|
import { RawSlots } from './componentSlots'
|
||||||
import { PatchFlags } from './patchFlags'
|
|
||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
import { isReactive } from '@vue/reactivity'
|
import { isReactive } from '@vue/reactivity'
|
||||||
import { AppContext } from './apiApp'
|
import { AppContext } from './apiApp'
|
||||||
@ -131,14 +131,11 @@ export function isVNode(value: any): boolean {
|
|||||||
|
|
||||||
export function createVNode(
|
export function createVNode(
|
||||||
type: VNodeTypes,
|
type: VNodeTypes,
|
||||||
props: { [key: string]: any } | null | 0 = null,
|
props: { [key: string]: any } | null = null,
|
||||||
children: any = null,
|
children: unknown = null,
|
||||||
patchFlag: number = 0,
|
patchFlag: number = 0,
|
||||||
dynamicProps: string[] | null = null
|
dynamicProps: string[] | null = null
|
||||||
): VNode {
|
): VNode {
|
||||||
// Allow passing 0 for props, this can save bytes on generated code.
|
|
||||||
props = props || null
|
|
||||||
|
|
||||||
// class & style normalization.
|
// class & style normalization.
|
||||||
if (props !== null) {
|
if (props !== null) {
|
||||||
// for reactive or proxy objects, we need to clone it to enable mutation.
|
// for reactive or proxy objects, we need to clone it to enable mutation.
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
export * from './patchFlags'
|
||||||
|
|
||||||
export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
|
export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
|
||||||
? Object.freeze({})
|
? Object.freeze({})
|
||||||
: {}
|
: {}
|
||||||
|
@ -17,12 +17,7 @@ export const enum PatchFlags {
|
|||||||
// Indicates an element with dynamic textContent (children fast path)
|
// Indicates an element with dynamic textContent (children fast path)
|
||||||
TEXT = 1,
|
TEXT = 1,
|
||||||
|
|
||||||
// Indicates an element with dynamic class.
|
// Indicates an element with dynamic class binding.
|
||||||
// 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 : '')
|
|
||||||
CLASS = 1 << 1,
|
CLASS = 1 << 1,
|
||||||
|
|
||||||
// Indicates an element with dynamic style
|
// Indicates an element with dynamic style
|
||||||
@ -48,6 +43,8 @@ export const enum PatchFlags {
|
|||||||
// Indicates an element that only needs non-props patching, e.g. ref or
|
// Indicates an element that only needs non-props patching, e.g. ref or
|
||||||
// directives (vnodeXXX hooks). It simply marks the vnode as "need patch",
|
// directives (vnodeXXX hooks). It simply marks the vnode as "need patch",
|
||||||
// since every pathced vnode checks for refs and vnodeXXX hooks.
|
// 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,
|
NEED_PATCH = 1 << 5,
|
||||||
|
|
||||||
// Indicates a fragment or element with keyed or partially-keyed v-for
|
// Indicates a fragment or element with keyed or partially-keyed v-for
|
Loading…
x
Reference in New Issue
Block a user