Skip to content

Commit 7b4a5a1

Browse files
committed
add warning for incorrect <slot> usage (ref vuejs#3447)
1 parent d927159 commit 7b4a5a1

File tree

2 files changed

+59
-8
lines changed

2 files changed

+59
-8
lines changed

src/compiler/parser/index.js

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ let preTransforms
3434
let transforms
3535
let postTransforms
3636
let delimiters
37+
let seenSlots: any
3738

3839
/**
3940
* Convert HTML string to AST.
@@ -50,6 +51,7 @@ export function parse (
5051
transforms = pluckModuleFunction(options.modules, 'transformNode')
5152
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
5253
delimiters = options.delimiters
54+
seenSlots = Object.create(null)
5355
const stack = []
5456
const preserveWhitespace = options.preserveWhitespace !== false
5557
let root
@@ -265,14 +267,7 @@ function processRef (el) {
265267
const ref = getBindingAttr(el, 'ref')
266268
if (ref) {
267269
el.ref = ref
268-
let parent = el
269-
while (parent) {
270-
if (parent.for !== undefined) {
271-
el.refInFor = true
272-
break
273-
}
274-
parent = parent.parent
275-
}
270+
el.refInFor = checkInFor(el)
276271
}
277272
}
278273

@@ -331,7 +326,25 @@ function processOnce (el) {
331326

332327
function processSlot (el) {
333328
if (el.tag === 'slot') {
329+
if (process.env.NODE_ENV !== 'production') {
330+
if (!el.attrsMap[':name'] && !el.attrsMap['v-bind:name'] && checkInFor(el)) {
331+
warn(
332+
'Static <slot> found inside v-for: they will not render correctly. ' +
333+
'Render the list in parent scope and use a single <slot> instead.'
334+
)
335+
}
336+
}
334337
el.slotName = getBindingAttr(el, 'name')
338+
if (process.env.NODE_ENV !== 'production') {
339+
const name = el.slotName
340+
if (seenSlots[name]) {
341+
warn(
342+
`Duplicate ${name ? `<slot> with name ${name}` : `default <slot>`} ` +
343+
`found in the same template.`
344+
)
345+
}
346+
seenSlots[name] = true
347+
}
335348
} else {
336349
const slotTarget = getBindingAttr(el, 'slot')
337350
if (slotTarget) {
@@ -405,6 +418,17 @@ function processAttrs (el) {
405418
}
406419
}
407420

421+
function checkInFor (el: ASTElement): boolean {
422+
let parent = el
423+
while (parent) {
424+
if (parent.for !== undefined) {
425+
return true
426+
}
427+
parent = parent.parent
428+
}
429+
return false
430+
}
431+
408432
function parseModifiers (name: string): Object | void {
409433
const match = name.match(modifierRE)
410434
if (match) {

test/unit/features/component/component-slot.spec.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,4 +454,31 @@ describe('Component slot', () => {
454454
expect(calls).toEqual([1, 2, 1, 2])
455455
}).then(done)
456456
})
457+
458+
it('warn duplicate slots', () => {
459+
new Vue({
460+
template: `<div>
461+
<slot></slot><slot></slot>
462+
<slot name="a"></slot><slot name="a"></slot>
463+
</div>`
464+
}).$mount()
465+
expect('Duplicate default <slot>').toHaveBeenWarned()
466+
expect('Duplicate <slot> with name "a"').toHaveBeenWarned()
467+
})
468+
469+
it('warn static slot inside v-for', () => {
470+
new Vue({
471+
template: `<div>
472+
<div v-for="i in 1"><slot :name="'test' + i"></slot></div>
473+
</div>`
474+
}).$mount()
475+
expect('Static <slot> found inside v-for').not.toHaveBeenWarned()
476+
477+
new Vue({
478+
template: `<div>
479+
<div v-for="i in 1"><slot></slot></div>
480+
</div>`
481+
}).$mount()
482+
expect('Static <slot> found inside v-for').toHaveBeenWarned()
483+
})
457484
})

0 commit comments

Comments
 (0)