fix(compiler): force block for custom dirs and inline beforeUpdate hooks
to ensure they are called before children updates
This commit is contained in:
parent
4b5d1ac894
commit
1c9a4810fc
@ -67,13 +67,13 @@ exports[`compiler: transform text element with custom directives and only one te
|
|||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveDirective: _resolveDirective, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveDirective: _resolveDirective, openBlock: _openBlock, createElementBlock: _createElementBlock, withDirectives: _withDirectives } = _Vue
|
||||||
|
|
||||||
const _directive_foo = _resolveDirective(\\"foo\\")
|
const _directive_foo = _resolveDirective(\\"foo\\")
|
||||||
|
|
||||||
return _withDirectives((_openBlock(), _createElementBlock(\\"p\\", null, [
|
return _withDirectives((_openBlock(), _createElementBlock(\\"p\\", null, [
|
||||||
_createTextVNode(_toDisplayString(foo), 1 /* TEXT */)
|
_createTextVNode(_toDisplayString(foo), 1 /* TEXT */)
|
||||||
], 512 /* NEED_PATCH */)), [
|
])), [
|
||||||
[_directive_foo]
|
[_directive_foo]
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -1202,6 +1202,23 @@ describe('compiler: element transform', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('force block for runtime custom directive w/ children', () => {
|
||||||
|
const { node } = parseWithElementTransform(`<div v-foo>hello</div>`)
|
||||||
|
expect(node.isBlock).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('force block for inline before-update handlers w/ children', () => {
|
||||||
|
expect(
|
||||||
|
parseWithElementTransform(`<div @vnode-before-update>hello</div>`).node
|
||||||
|
.isBlock
|
||||||
|
).toBe(true)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
parseWithElementTransform(`<div @vnodeBeforeUpdate>hello</div>`).node
|
||||||
|
.isBlock
|
||||||
|
).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
// #938
|
// #938
|
||||||
test('element with dynamic keys should be forced into blocks', () => {
|
test('element with dynamic keys should be forced into blocks', () => {
|
||||||
const ast = parse(`<div><div :key="foo" /></div>`)
|
const ast = parse(`<div><div :key="foo" /></div>`)
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
advancePositionWithMutation,
|
advancePositionWithMutation,
|
||||||
advancePositionWithClone,
|
advancePositionWithClone,
|
||||||
isCoreComponent,
|
isCoreComponent,
|
||||||
isBindKey
|
isStaticArgOf
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import {
|
import {
|
||||||
Namespaces,
|
Namespaces,
|
||||||
@ -681,7 +681,7 @@ function isComponent(
|
|||||||
} else if (
|
} else if (
|
||||||
// :is on plain element - only treat as component in compat mode
|
// :is on plain element - only treat as component in compat mode
|
||||||
p.name === 'bind' &&
|
p.name === 'bind' &&
|
||||||
isBindKey(p.arg, 'is') &&
|
isStaticArgOf(p.arg, 'is') &&
|
||||||
__COMPAT__ &&
|
__COMPAT__ &&
|
||||||
checkCompatEnabled(
|
checkCompatEnabled(
|
||||||
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
|
||||||
|
@ -168,6 +168,13 @@ export function getConstantType(
|
|||||||
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
|
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
|
||||||
return ConstantTypes.NOT_CONSTANT
|
return ConstantTypes.NOT_CONSTANT
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
codegenNode.isBlock &&
|
||||||
|
node.tag !== 'svg' &&
|
||||||
|
node.tag !== 'foreignObject'
|
||||||
|
) {
|
||||||
|
return ConstantTypes.NOT_CONSTANT
|
||||||
|
}
|
||||||
const flag = getPatchFlag(codegenNode)
|
const flag = getPatchFlag(codegenNode)
|
||||||
if (!flag) {
|
if (!flag) {
|
||||||
let returnType = ConstantTypes.CAN_STRINGIFY
|
let returnType = ConstantTypes.CAN_STRINGIFY
|
||||||
|
@ -56,7 +56,7 @@ import {
|
|||||||
toValidAssetId,
|
toValidAssetId,
|
||||||
findProp,
|
findProp,
|
||||||
isCoreComponent,
|
isCoreComponent,
|
||||||
isBindKey,
|
isStaticArgOf,
|
||||||
findDir,
|
findDir,
|
||||||
isStaticExp
|
isStaticExp
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
@ -120,10 +120,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
// updates inside get proper isSVG flag at runtime. (#639, #643)
|
// updates inside get proper isSVG flag at runtime. (#639, #643)
|
||||||
// This is technically web-specific, but splitting the logic out of core
|
// This is technically web-specific, but splitting the logic out of core
|
||||||
// leads to too much unnecessary complexity.
|
// leads to too much unnecessary complexity.
|
||||||
(tag === 'svg' ||
|
(tag === 'svg' || tag === 'foreignObject'))
|
||||||
tag === 'foreignObject' ||
|
|
||||||
// #938: elements with dynamic keys should be forced into blocks
|
|
||||||
findProp(node, 'key', true)))
|
|
||||||
|
|
||||||
// props
|
// props
|
||||||
if (props.length > 0) {
|
if (props.length > 0) {
|
||||||
@ -138,6 +135,10 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
directives.map(dir => buildDirectiveArgs(dir, context))
|
directives.map(dir => buildDirectiveArgs(dir, context))
|
||||||
) as DirectiveArguments)
|
) as DirectiveArguments)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
if (propsBuildResult.shouldUseBlock) {
|
||||||
|
shouldUseBlock = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// children
|
// children
|
||||||
@ -386,12 +387,15 @@ export function buildProps(
|
|||||||
directives: DirectiveNode[]
|
directives: DirectiveNode[]
|
||||||
patchFlag: number
|
patchFlag: number
|
||||||
dynamicPropNames: string[]
|
dynamicPropNames: string[]
|
||||||
|
shouldUseBlock: boolean
|
||||||
} {
|
} {
|
||||||
const { tag, loc: elementLoc } = node
|
const { tag, loc: elementLoc, children } = node
|
||||||
const isComponent = node.tagType === ElementTypes.COMPONENT
|
const isComponent = node.tagType === ElementTypes.COMPONENT
|
||||||
let properties: ObjectExpression['properties'] = []
|
let properties: ObjectExpression['properties'] = []
|
||||||
const mergeArgs: PropsExpression[] = []
|
const mergeArgs: PropsExpression[] = []
|
||||||
const runtimeDirectives: DirectiveNode[] = []
|
const runtimeDirectives: DirectiveNode[] = []
|
||||||
|
const hasChildren = children.length > 0
|
||||||
|
let shouldUseBlock = false
|
||||||
|
|
||||||
// patchFlag analysis
|
// patchFlag analysis
|
||||||
let patchFlag = 0
|
let patchFlag = 0
|
||||||
@ -526,7 +530,7 @@ export function buildProps(
|
|||||||
if (
|
if (
|
||||||
name === 'is' ||
|
name === 'is' ||
|
||||||
(isVBind &&
|
(isVBind &&
|
||||||
isBindKey(arg, 'is') &&
|
isStaticArgOf(arg, 'is') &&
|
||||||
(isComponentTag(tag) ||
|
(isComponentTag(tag) ||
|
||||||
(__COMPAT__ &&
|
(__COMPAT__ &&
|
||||||
isCompatEnabled(
|
isCompatEnabled(
|
||||||
@ -541,6 +545,16 @@ export function buildProps(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
// #938: elements with dynamic keys should be forced into blocks
|
||||||
|
(isVBind && isStaticArgOf(arg, 'key')) ||
|
||||||
|
// inline before-update hooks need to force block so that it is invoked
|
||||||
|
// before children
|
||||||
|
(isVOn && hasChildren && isStaticArgOf(arg, 'vnodeBeforeUpdate', true))
|
||||||
|
) {
|
||||||
|
shouldUseBlock = true
|
||||||
|
}
|
||||||
|
|
||||||
// special case for v-bind and v-on with no argument
|
// special case for v-bind and v-on with no argument
|
||||||
if (!arg && (isVBind || isVOn)) {
|
if (!arg && (isVBind || isVOn)) {
|
||||||
hasDynamicKeys = true
|
hasDynamicKeys = true
|
||||||
@ -633,6 +647,11 @@ export function buildProps(
|
|||||||
} else {
|
} else {
|
||||||
// no built-in transform, this is a user custom directive.
|
// no built-in transform, this is a user custom directive.
|
||||||
runtimeDirectives.push(prop)
|
runtimeDirectives.push(prop)
|
||||||
|
// custom dirs may use beforeUpdate so they need to force blocks
|
||||||
|
// to ensure before-update gets called before children update
|
||||||
|
if (hasChildren) {
|
||||||
|
shouldUseBlock = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -700,6 +719,7 @@ export function buildProps(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
!shouldUseBlock &&
|
||||||
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
|
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
|
||||||
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
|
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
|
||||||
) {
|
) {
|
||||||
@ -784,7 +804,8 @@ export function buildProps(
|
|||||||
props: propsExpression,
|
props: propsExpression,
|
||||||
directives: runtimeDirectives,
|
directives: runtimeDirectives,
|
||||||
patchFlag,
|
patchFlag,
|
||||||
dynamicPropNames
|
dynamicPropNames,
|
||||||
|
shouldUseBlock
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
SlotOutletNode,
|
SlotOutletNode,
|
||||||
createFunctionExpression
|
createFunctionExpression
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { isSlotOutlet, isBindKey, isStaticExp } from '../utils'
|
import { isSlotOutlet, isStaticArgOf, isStaticExp } from '../utils'
|
||||||
import { buildProps, PropsExpression } from './transformElement'
|
import { buildProps, PropsExpression } from './transformElement'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { RENDER_SLOT } from '../runtimeHelpers'
|
import { RENDER_SLOT } from '../runtimeHelpers'
|
||||||
@ -75,7 +75,7 @@ export function processSlotOutlet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (p.name === 'bind' && isBindKey(p.arg, 'name')) {
|
if (p.name === 'bind' && isStaticArgOf(p.arg, 'name')) {
|
||||||
if (p.exp) slotName = p.exp
|
if (p.exp) slotName = p.exp
|
||||||
} else {
|
} else {
|
||||||
if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {
|
if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {
|
||||||
|
@ -42,7 +42,14 @@ import {
|
|||||||
WITH_MEMO,
|
WITH_MEMO,
|
||||||
OPEN_BLOCK
|
OPEN_BLOCK
|
||||||
} from './runtimeHelpers'
|
} from './runtimeHelpers'
|
||||||
import { isString, isObject, hyphenate, extend, NOOP } from '@vue/shared'
|
import {
|
||||||
|
isString,
|
||||||
|
isObject,
|
||||||
|
hyphenate,
|
||||||
|
extend,
|
||||||
|
NOOP,
|
||||||
|
camelize
|
||||||
|
} from '@vue/shared'
|
||||||
import { PropsExpression } from './transforms/transformElement'
|
import { PropsExpression } from './transforms/transformElement'
|
||||||
import { parseExpression } from '@babel/parser'
|
import { parseExpression } from '@babel/parser'
|
||||||
import { Expression } from '@babel/types'
|
import { Expression } from '@babel/types'
|
||||||
@ -282,15 +289,23 @@ export function findProp(
|
|||||||
} else if (
|
} else if (
|
||||||
p.name === 'bind' &&
|
p.name === 'bind' &&
|
||||||
(p.exp || allowEmpty) &&
|
(p.exp || allowEmpty) &&
|
||||||
isBindKey(p.arg, name)
|
isStaticArgOf(p.arg, name)
|
||||||
) {
|
) {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isBindKey(arg: DirectiveNode['arg'], name: string): boolean {
|
export function isStaticArgOf(
|
||||||
return !!(arg && isStaticExp(arg) && arg.content === name)
|
arg: DirectiveNode['arg'],
|
||||||
|
name: string,
|
||||||
|
camel?: boolean
|
||||||
|
): boolean {
|
||||||
|
return !!(
|
||||||
|
arg &&
|
||||||
|
isStaticExp(arg) &&
|
||||||
|
(camel ? camelize(arg.content) : arg.content) === name
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasDynamicKeyVBind(node: ElementNode): boolean {
|
export function hasDynamicKeyVBind(node: ElementNode): boolean {
|
||||||
@ -371,7 +386,8 @@ export function injectProp(
|
|||||||
*
|
*
|
||||||
* we need to get the real props before normalization
|
* we need to get the real props before normalization
|
||||||
*/
|
*/
|
||||||
let props = node.type === NodeTypes.VNODE_CALL ? node.props : node.arguments[2]
|
let props =
|
||||||
|
node.type === NodeTypes.VNODE_CALL ? node.props : node.arguments[2]
|
||||||
let callPath: CallExpression[] = []
|
let callPath: CallExpression[] = []
|
||||||
let parentCall: CallExpression | undefined
|
let parentCall: CallExpression | undefined
|
||||||
if (
|
if (
|
||||||
|
@ -22,7 +22,7 @@ import {
|
|||||||
TextNode,
|
TextNode,
|
||||||
hasDynamicKeyVBind,
|
hasDynamicKeyVBind,
|
||||||
MERGE_PROPS,
|
MERGE_PROPS,
|
||||||
isBindKey,
|
isStaticArgOf,
|
||||||
createSequenceExpression,
|
createSequenceExpression,
|
||||||
InterpolationNode,
|
InterpolationNode,
|
||||||
isStaticExp,
|
isStaticExp,
|
||||||
@ -335,7 +335,7 @@ function isTextareaWithValue(
|
|||||||
return !!(
|
return !!(
|
||||||
node.tag === 'textarea' &&
|
node.tag === 'textarea' &&
|
||||||
prop.name === 'bind' &&
|
prop.name === 'bind' &&
|
||||||
isBindKey(prop.arg, 'value')
|
isStaticArgOf(prop.arg, 'value')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user