Skip to content

Commit

Permalink
dx(compiler-core): warn on <template v-for> key misplacement
Browse files Browse the repository at this point in the history
Note: the behavior is different from Vue 2. `<template v-for>` are compiled
into an array of Fragment vnodes so the key should be placed the `<template>`
for v-for to use it for diffing.
  • Loading branch information
yyx990803 committed Aug 4, 2020
1 parent de0c8a7 commit b0d01e9
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 2 deletions.
28 changes: 28 additions & 0 deletions packages/compiler-core/__tests__/transforms/vFor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,34 @@ describe('compiler: v-for', () => {
})
)
})

test('<template v-for> key placement', () => {
const onError = jest.fn()
parseWithForTransform(
`
<template v-for="item in items">
<div :key="item.id"/>
</template>`,
{ onError }
)

expect(onError).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({
code: ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT
})
)

// should not warn on nested v-for keys
parseWithForTransform(
`
<template v-for="item in items">
<div v-for="c in item.children" :key="c.id"/>
</template>`,
{ onError }
)
expect(onError).toHaveBeenCalledTimes(1)
})
})

describe('source location', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler-core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const enum ErrorCodes {
X_V_ELSE_NO_ADJACENT_IF,
X_V_FOR_NO_EXPRESSION,
X_V_FOR_MALFORMED_EXPRESSION,
X_V_FOR_TEMPLATE_KEY_PLACEMENT,
X_V_BIND_NO_EXPRESSION,
X_V_ON_NO_EXPRESSION,
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
Expand Down Expand Up @@ -140,6 +141,7 @@ export const errorMessages: { [code: number]: string } = {
[ErrorCodes.X_V_ELSE_NO_ADJACENT_IF]: `v-else/v-else-if has no adjacent v-if.`,
[ErrorCodes.X_V_FOR_NO_EXPRESSION]: `v-for is missing expression.`,
[ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
[ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT]: `<template v-for> key should be placed on the <template> tag.`,
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
Expand Down
22 changes: 20 additions & 2 deletions packages/compiler-core/src/transforms/vFor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
SimpleExpressionNode,
createCallExpression,
createFunctionExpression,
ElementTypes,
createObjectExpression,
createObjectProperty,
ForCodegenNode,
Expand Down Expand Up @@ -81,6 +80,25 @@ export const transformFor = createStructuralDirectiveTransform(
let childBlock: BlockCodegenNode
const isTemplate = isTemplateNode(node)
const { children } = forNode

// check <template v-for> key placement
if ((__DEV__ || !__BROWSER__) && isTemplate) {
node.children.some(c => {
if (c.type === NodeTypes.ELEMENT) {
const key = findProp(c, 'key')
if (key) {
context.onError(
createCompilerError(
ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT,
key.loc
)
)
return true
}
}
})
}

const needFragmentWrapper =
children.length !== 1 || children[0].type !== NodeTypes.ELEMENT
const slotOutlet = isSlotOutlet(node)
Expand Down Expand Up @@ -183,7 +201,7 @@ export function processFor(
keyAlias: key,
objectIndexAlias: index,
parseResult,
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
children: isTemplateNode(node) ? node.children : [node]
}

context.replaceNode(forNode)
Expand Down

0 comments on commit b0d01e9

Please sign in to comment.