Skip to content

Commit

Permalink
fix(vuejs#7913): Prevent erroneous warning when using <slot> inside s…
Browse files Browse the repository at this point in the history
…lot-scope

Because slotNodes inside a slot-scope context are already set to _rendered = true after initial
render, the warning for duplicate slot presence always fires when a slot-scope prop change triggers
a re-render. With this change, the compiler tracks whether any slot-scoped elements have been
encountered at the point the slot is compiled. If so, the direct ancestors of the slot are checked
for slot-scope presence, and if found, the warning is supressed. This is admittedly not a perfect
solution, as within a slot-scope context the warning now does not fire even when there _are_
duplicate slots, but I couldn't find a good way to get around that.

fix vuejs#7913
  • Loading branch information
mpbarlow committed May 26, 2018
1 parent 5e3823a commit 3e0aef2
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 15 deletions.
23 changes: 13 additions & 10 deletions src/compiler/codegen/index.js
Expand Up @@ -18,6 +18,7 @@ export class CodegenState {
maybeComponent: (el: ASTElement) => boolean;
onceId: number;
staticRenderFns: Array<string>;
slotScopeinAst: boolean;

constructor (options: CompilerOptions) {
this.options = options
Expand All @@ -29,6 +30,8 @@ export class CodegenState {
this.maybeComponent = (el: ASTElement) => !isReservedTag(el.tag)
this.onceId = 0
this.staticRenderFns = []
// we can skip checks for slot-scope parents if we haven't seen any
this.slotScopeinAst = false
}
}

Expand All @@ -50,6 +53,7 @@ export function generate (
}

export function genElement (el: ASTElement, state: CodegenState): string {
if (el.slotScope) state.slotScopeinAst = true
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
Expand Down Expand Up @@ -456,19 +460,18 @@ export function genComment (comment: ASTText): string {
function genSlot (el: ASTElement, state: CodegenState): string {
const slotName = el.slotName || '"default"'
const children = genChildren(el, state)
let res = `_t(${slotName}${children ? `,${children}` : ''}`
const attrs = el.attrs && `{${el.attrs.map(a => `${camelize(a.name)}:${a.value}`).join(',')}}`
const bind = el.attrsMap['v-bind']
if ((attrs || bind) && !children) {
res += `,null`
}
if (attrs) {
res += `,${attrs}`
}
if (bind) {
res += `${attrs ? '' : ',null'},${bind}`
let inScopedSlot = false
let ancestor = el
if (process.env.NODE_ENV !== 'production' && state.slotScopeinAst) {
while (!inScopedSlot && ancestor.parent) {
if (ancestor.slotScope) inScopedSlot = true
ancestor = ancestor.parent
}
}
return res + ')'
return `_t(${slotName},${children || 'null'},${attrs || 'null'},` +
`${bind || 'null'},${inScopedSlot ? 'true' : 'false'})`
}

// componentName is el.component, take it as argument to shun flow's pessimistic refinement
Expand Down
5 changes: 3 additions & 2 deletions src/core/instance/render-helpers/render-slot.js
Expand Up @@ -9,7 +9,8 @@ export function renderSlot (
name: string,
fallback: ?Array<VNode>,
props: ?Object,
bindObject: ?Object
bindObject: ?Object,
inSlotScope: ?boolean
): ?Array<VNode> {
const scopedSlotFn = this.$scopedSlots[name]
let nodes
Expand All @@ -29,7 +30,7 @@ export function renderSlot (
const slotNodes = this.$slots[name]
// warn duplicate slot usage
if (slotNodes) {
if (process.env.NODE_ENV !== 'production' && slotNodes._rendered) {
if (process.env.NODE_ENV !== 'production' && !inSlotScope && slotNodes._rendered) {
warn(
`Duplicate presence of slot "${name}" found in the same render tree ` +
`- this will likely cause render errors.`,
Expand Down
6 changes: 3 additions & 3 deletions test/unit/modules/compiler/codegen.spec.js
Expand Up @@ -161,21 +161,21 @@ describe('codegen', () => {
it('generate single slot', () => {
assertCodegen(
'<div><slot></slot></div>',
`with(this){return _c('div',[_t("default")],2)}`
`with(this){return _c('div',[_t("default",null,null,null,false)],2)}`
)
})

it('generate named slot', () => {
assertCodegen(
'<div><slot name="one"></slot></div>',
`with(this){return _c('div',[_t("one")],2)}`
`with(this){return _c('div',[_t("one",null,null,null,false)],2)}`
)
})

it('generate slot fallback content', () => {
assertCodegen(
'<div><slot><div>hi</div></slot></div>',
`with(this){return _c('div',[_t("default",[_c('div',[_v("hi")])])],2)}`
`with(this){return _c('div',[_t("default",[_c('div',[_v("hi")])],null,null,false)],2)}`
)
})

Expand Down

0 comments on commit 3e0aef2

Please sign in to comment.