Skip to content

Commit 4df7619

Browse files
authoredMar 23, 2020
fix: Add v-slot support in scopedSlots property, fix #1457 (#1485)
* feat: update scope-slot detection * fix: fix issue with empty template tag when using v-slot * test: add more v-slot tests
1 parent 98b37f1 commit 4df7619

File tree

2 files changed

+88
-5
lines changed

2 files changed

+88
-5
lines changed
 

‎packages/create-instance/create-scoped-slots.js

+38-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { throwError } from 'shared/util'
55
import { VUE_VERSION } from 'shared/consts'
66

77
function isDestructuringSlotScope(slotScope: string): boolean {
8-
return slotScope[0] === '{' && slotScope[slotScope.length - 1] === '}'
8+
return /^{.*}$/.test(slotScope)
99
}
1010

1111
function getVueTemplateCompilerHelpers(
@@ -46,7 +46,36 @@ function validateEnvironment(): void {
4646
}
4747
}
4848

49-
const slotScopeRe = /<[^>]+ slot-scope=\"(.+)\"/
49+
function isScopedSlot(slot) {
50+
if (typeof slot === 'function') return { match: null, slot }
51+
52+
const slotScopeRe = /<[^>]+ slot-scope="(.+)"/
53+
const vSlotRe = /<template v-slot(?::.+)?="(.+)"/
54+
const shortVSlotRe = /<template #.*="(.+)"/
55+
56+
const hasOldSlotScope = slot.match(slotScopeRe)
57+
const hasVSlotScopeAttr = slot.match(vSlotRe)
58+
const hasShortVSlotScopeAttr = slot.match(shortVSlotRe)
59+
60+
if (hasOldSlotScope) {
61+
return { slot, match: hasOldSlotScope }
62+
} else if (hasVSlotScopeAttr || hasShortVSlotScopeAttr) {
63+
// Strip v-slot and #slot attributes from `template` tag. compileToFunctions leaves empty `template` tag otherwise.
64+
const sanitizedSlot = slot.replace(
65+
/(<template)([^>]+)(>.+<\/template>)/,
66+
'$1$3'
67+
)
68+
return {
69+
slot: sanitizedSlot,
70+
match: hasVSlotScopeAttr || hasShortVSlotScopeAttr
71+
}
72+
}
73+
// we have no matches, so we just return
74+
return {
75+
slot: slot,
76+
match: null
77+
}
78+
}
5079

5180
// Hide warning about <template> disallowed as root element
5281
function customWarn(msg) {
@@ -70,14 +99,18 @@ export default function createScopedSlots(
7099
for (const scopedSlotName in scopedSlotsOption) {
71100
const slot = scopedSlotsOption[scopedSlotName]
72101
const isFn = typeof slot === 'function'
102+
103+
const scopedSlotMatches = isScopedSlot(slot)
104+
73105
// Type check to silence flow (can't use isFn)
74106
const renderFn =
75107
typeof slot === 'function'
76108
? slot
77-
: compileToFunctions(slot, { warn: customWarn }).render
109+
: compileToFunctions(scopedSlotMatches.slot, { warn: customWarn })
110+
.render
111+
112+
const slotScope = scopedSlotMatches.match && scopedSlotMatches.match[1]
78113

79-
const hasSlotScopeAttr = !isFn && slot.match(slotScopeRe)
80-
const slotScope = hasSlotScopeAttr && hasSlotScopeAttr[1]
81114
scopedSlots[scopedSlotName] = function(props) {
82115
let res
83116
if (isFn) {

‎test/specs/mounting-options/scopedSlots.spec.js

+50
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,56 @@ describeWithShallowAndMount('scopedSlots', mountingMethod => {
281281
}
282282
)
283283

284+
itDoNotRunIf(
285+
vueVersion < 2.6,
286+
'renders scoped slot with v-slot syntax',
287+
() => {
288+
const TestComponent = {
289+
data() {
290+
return {
291+
val: 25,
292+
val2: 50
293+
}
294+
},
295+
template:
296+
'<div><slot :val="val"/><slot name="named" :val="val2"/></div>'
297+
}
298+
const wrapper = mountingMethod(TestComponent, {
299+
scopedSlots: {
300+
default:
301+
'<template v-slot:default="{ val }"><p>{{ val }}</p></template>',
302+
named:
303+
'<template v-slot:named="prop"><p>{{ prop.val }}</p></template>'
304+
}
305+
})
306+
expect(wrapper.html()).to.equal('<div>\n <p>25</p>\n <p>50</p>\n</div>')
307+
}
308+
)
309+
310+
itDoNotRunIf(
311+
vueVersion < 2.6,
312+
'renders scoped slot with shorthand v-slot syntax',
313+
() => {
314+
const TestComponent = {
315+
data() {
316+
return {
317+
val: 25,
318+
val2: 50
319+
}
320+
},
321+
template:
322+
'<div><slot :val="val"/><slot name="named" :val="val2"/></div>'
323+
}
324+
const wrapper = mountingMethod(TestComponent, {
325+
scopedSlots: {
326+
default: '<template #default="{ val }"><p>{{ val }}</p></template>',
327+
named: '<template #named="prop"><p>{{ prop.val }}</p></template>'
328+
}
329+
})
330+
expect(wrapper.html()).to.equal('<div>\n <p>25</p>\n <p>50</p>\n</div>')
331+
}
332+
)
333+
284334
itDoNotRunIf(
285335
vueVersion < 2.5 || mountingMethod.name !== 'mount',
286336
'renders using localVue constructor',

0 commit comments

Comments
 (0)
Please sign in to comment.