return function render(_ctx, _cache) {
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\\")
return _withDirectives((_openBlock(), _createElementBlock(\\"p\\", null, [
_createTextVNode(_toDisplayString(foo), 1 /* TEXT */)
- ], 512 /* NEED_PATCH */)), [
+ ])), [
[_directive_foo]
])
}
})
})
+ 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
test('element with dynamic keys should be forced into blocks', () => {
const ast = parse(`<div><div :key="foo" /></div>`)
advancePositionWithMutation,
advancePositionWithClone,
isCoreComponent,
- isBindKey
+ isStaticArgOf
} from './utils'
import {
Namespaces,
} else if (
// :is on plain element - only treat as component in compat mode
p.name === 'bind' &&
- isBindKey(p.arg, 'is') &&
+ isStaticArgOf(p.arg, 'is') &&
__COMPAT__ &&
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_IS_ON_ELEMENT,
if (codegenNode.type !== NodeTypes.VNODE_CALL) {
return ConstantTypes.NOT_CONSTANT
}
+ if (
+ codegenNode.isBlock &&
+ node.tag !== 'svg' &&
+ node.tag !== 'foreignObject'
+ ) {
+ return ConstantTypes.NOT_CONSTANT
+ }
const flag = getPatchFlag(codegenNode)
if (!flag) {
let returnType = ConstantTypes.CAN_STRINGIFY
toValidAssetId,
findProp,
isCoreComponent,
- isBindKey,
+ isStaticArgOf,
findDir,
isStaticExp
} from '../utils'
// updates inside get proper isSVG flag at runtime. (#639, #643)
// This is technically web-specific, but splitting the logic out of core
// leads to too much unnecessary complexity.
- (tag === 'svg' ||
- tag === 'foreignObject' ||
- // #938: elements with dynamic keys should be forced into blocks
- findProp(node, 'key', true)))
+ (tag === 'svg' || tag === 'foreignObject'))
// props
if (props.length > 0) {
directives.map(dir => buildDirectiveArgs(dir, context))
) as DirectiveArguments)
: undefined
+
+ if (propsBuildResult.shouldUseBlock) {
+ shouldUseBlock = true
+ }
}
// children
directives: DirectiveNode[]
patchFlag: number
dynamicPropNames: string[]
+ shouldUseBlock: boolean
} {
- const { tag, loc: elementLoc } = node
+ const { tag, loc: elementLoc, children } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
let properties: ObjectExpression['properties'] = []
const mergeArgs: PropsExpression[] = []
const runtimeDirectives: DirectiveNode[] = []
+ const hasChildren = children.length > 0
+ let shouldUseBlock = false
// patchFlag analysis
let patchFlag = 0
if (
name === 'is' ||
(isVBind &&
- isBindKey(arg, 'is') &&
+ isStaticArgOf(arg, 'is') &&
(isComponentTag(tag) ||
(__COMPAT__ &&
isCompatEnabled(
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
if (!arg && (isVBind || isVOn)) {
hasDynamicKeys = true
} else {
// no built-in transform, this is a user custom directive.
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
+ }
}
}
}
}
if (
+ !shouldUseBlock &&
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
(hasRef || hasVnodeHook || runtimeDirectives.length > 0)
) {
props: propsExpression,
directives: runtimeDirectives,
patchFlag,
- dynamicPropNames
+ dynamicPropNames,
+ shouldUseBlock
}
}
SlotOutletNode,
createFunctionExpression
} from '../ast'
-import { isSlotOutlet, isBindKey, isStaticExp } from '../utils'
+import { isSlotOutlet, isStaticArgOf, isStaticExp } from '../utils'
import { buildProps, PropsExpression } from './transformElement'
import { createCompilerError, ErrorCodes } from '../errors'
import { RENDER_SLOT } from '../runtimeHelpers'
}
}
} else {
- if (p.name === 'bind' && isBindKey(p.arg, 'name')) {
+ if (p.name === 'bind' && isStaticArgOf(p.arg, 'name')) {
if (p.exp) slotName = p.exp
} else {
if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {
WITH_MEMO,
OPEN_BLOCK
} 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 { parseExpression } from '@babel/parser'
import { Expression } from '@babel/types'
} else if (
p.name === 'bind' &&
(p.exp || allowEmpty) &&
- isBindKey(p.arg, name)
+ isStaticArgOf(p.arg, name)
) {
return p
}
}
}
-export function isBindKey(arg: DirectiveNode['arg'], name: string): boolean {
- return !!(arg && isStaticExp(arg) && arg.content === name)
+export function isStaticArgOf(
+ 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 {
*
* 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 parentCall: CallExpression | undefined
if (
TextNode,
hasDynamicKeyVBind,
MERGE_PROPS,
- isBindKey,
+ isStaticArgOf,
createSequenceExpression,
InterpolationNode,
isStaticExp,
return !!(
node.tag === 'textarea' &&
prop.name === 'bind' &&
- isBindKey(prop.arg, 'value')
+ isStaticArgOf(prop.arg, 'value')
)
}