Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions src/compiler/parser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,29 +124,37 @@ export function parse (
processAttrs(element)
}

// tree management
if (!root) {
root = element
// check root element constraints
function checkRootConstraints (el) {
if (process.env.NODE_ENV !== 'production') {
if (tag === 'slot' || tag === 'template') {
if (el.tag === 'slot' || el.tag === 'template') {
warn(
`Cannot use <${tag}> as component root element because it may ` +
`Cannot use <${el.tag}> as component root element because it may ` +
'contain multiple nodes:\n' + template
)
}
if (element.attrsMap.hasOwnProperty('v-for')) {
if (el.attrsMap.hasOwnProperty('v-for')) {
warn(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements:\n' + template
)
}
}
}

// tree management
if (!root) {
root = element
checkRootConstraints(root)
} else if (process.env.NODE_ENV !== 'production' && !stack.length && !warned) {
warned = true
warn(
`Component template should contain exactly one root element:\n\n${template}`
)
// allow 2 root elements with v-if and v-else
if ((root.attrsMap.hasOwnProperty('v-if') && element.attrsMap.hasOwnProperty('v-else'))) {
checkRootConstraints(element)
} else {
warned = true
warn(
`Component template should contain exactly one root element:\n\n${template}`
)
}
}
if (currentParent && !element.forbidden) {
if (element.else) {
Expand Down
29 changes: 29 additions & 0 deletions test/unit/modules/compiler/parser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,35 @@ describe('parser', () => {
expect('Component template should contain exactly one root element').toHaveBeenWarned()
})

it('warn multiple root elements', () => {
parse('<div></div><div></div>', baseOptions)
expect('Component template should contain exactly one root element:\n\n<div></div><div></div>').toHaveBeenWarned()
})

it('not warn 2 root elements with v-if and v-else', () => {
parse('<div v-if="1"></div><div v-else></div>', baseOptions)
expect('Component template should contain exactly one root element:\n\n<div v-if="1"></div><div v-else></div>')
.not.toHaveBeenWarned()
})

it('warn 2 root elements with v-if', () => {
parse('<div v-if="1"></div><div v-if="2"></div>', baseOptions)
expect('Component template should contain exactly one root element:\n\n<div v-if="1"></div><div v-if="2"></div>')
.toHaveBeenWarned()
})

it('warn 3 root elements with v-if and v-else on first 2', () => {
parse('<div v-if="1"></div><div v-else></div><div></div>', baseOptions)
expect('Component template should contain exactly one root element:\n\n<div v-if="1"></div><div v-else></div><div></div>')
.toHaveBeenWarned()
})

it('warn 2 root elements with v-if and v-else with v-for on 2nd', () => {
parse('<div v-if="1"></div><div v-else v-for="i in [1]"></div>', baseOptions)
expect('Cannot use v-for on stateful component root element because it renders multiple elements:\n<div v-if="1"></div><div v-else v-for="i in [1]"></div>')
.toHaveBeenWarned()
})

it('warn <template> as root element', () => {
parse('<template></template>', baseOptions)
expect('Cannot use <template> as component root element').toHaveBeenWarned()
Expand Down