Skip to content

Commit 7f1f8d5

Browse files
committed
fix: improve scopedSlots option
1 parent 96f1e39 commit 7f1f8d5

File tree

5 files changed

+134
-104
lines changed

5 files changed

+134
-104
lines changed

packages/create-instance/create-instance.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import extractInstanceOptions from './extract-instance-options'
1010
import createFunctionalComponent from './create-functional-component'
1111
import { componentNeedsCompiling } from 'shared/validators'
1212
import { validateSlots } from './validate-slots'
13+
import getScopedSlots from './get-scoped-slots'
1314

1415
export default function createInstance (
1516
component: Component,
@@ -122,6 +123,8 @@ export default function createInstance (
122123
options.provide = () => obj
123124
}
124125

126+
const scopedSlots = getScopedSlots(options.scopedSlots)
127+
125128
const Parent = _Vue.extend({
126129
provide: options.provide,
127130
render (h) {
@@ -134,7 +137,8 @@ export default function createInstance (
134137
ref: 'vm',
135138
props: options.propsData,
136139
on: options.listeners,
137-
attrs: options.attrs
140+
attrs: options.attrs,
141+
scopedSlots
138142
},
139143
slots
140144
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// @flow
2+
3+
import Vue from 'vue'
4+
import { compileToFunctions } from 'vue-template-compiler'
5+
import { throwError, vueVersion } from 'shared/util'
6+
7+
function isDestructuringSlotScope (slotScope: string): boolean {
8+
return slotScope[0] === '{' && slotScope[slotScope.length - 1] === '}'
9+
}
10+
11+
function getVueTemplateCompilerHelpers (): { [name: string]: Function } {
12+
const vue = new Vue()
13+
const helpers = {}
14+
const names = [
15+
'_c',
16+
'_o',
17+
'_n',
18+
'_s',
19+
'_l',
20+
'_t',
21+
'_q',
22+
'_i',
23+
'_m',
24+
'_f',
25+
'_k',
26+
'_b',
27+
'_v',
28+
'_e',
29+
'_u',
30+
'_g'
31+
]
32+
names.forEach(name => {
33+
helpers[name] = vue._renderProxy[name]
34+
})
35+
return helpers
36+
}
37+
38+
function validateEnvironment (): void {
39+
if (window.navigator.userAgent.match(/PhantomJS/i)) {
40+
throwError(
41+
`the scopedSlots option does not support PhantomJS. ` +
42+
`Please use Puppeteer, or pass a component.`
43+
)
44+
}
45+
if (vueVersion < 2.5) {
46+
throwError(`the scopedSlots option is only supported in ` + `[email protected]+.`)
47+
}
48+
}
49+
50+
function validateTempldate (template: string): void {
51+
if (template.trim().substr(0, 9) === '<template') {
52+
throwError(
53+
`the scopedSlots option does not support a template ` +
54+
`tag as the root element.`
55+
)
56+
}
57+
}
58+
59+
export default function getScopedSlots (
60+
scopedSlotsOption: ?{ [slotName: string]: string }
61+
): { [slotName: string]: (props: Object) => VNode } {
62+
const scopedSlots = {}
63+
if (!scopedSlotsOption) {
64+
return scopedSlots
65+
}
66+
validateEnvironment()
67+
const helpers = getVueTemplateCompilerHelpers()
68+
for (const name in scopedSlotsOption) {
69+
const template = scopedSlotsOption[name]
70+
validateTempldate(template)
71+
const render = compileToFunctions(template).render
72+
const domParser = new window.DOMParser()
73+
const _document = domParser.parseFromString(template, 'text/html')
74+
const slotScope = _document.body.firstChild.getAttribute(
75+
'slot-scope'
76+
)
77+
const isDestructuring = isDestructuringSlotScope(slotScope)
78+
scopedSlots[name] = function (props) {
79+
if (isDestructuring) {
80+
return render.call({ ...helpers, ...props })
81+
} else {
82+
return render.call({ ...helpers, [slotScope]: props })
83+
}
84+
}
85+
}
86+
return scopedSlots
87+
}

packages/test-utils/src/add-scoped-slots.js

-92
This file was deleted.

packages/test-utils/src/mount.js

-11
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { findAllVueComponentsFromVm } from './find-vue-components'
1212
import { mergeOptions } from 'shared/merge-options'
1313
import config from './config'
1414
import warnIfNoWindow from './warn-if-no-window'
15-
import { addScopedSlots } from './add-scoped-slots'
1615

1716
Vue.config.productionTip = false
1817
Vue.config.devtools = false
@@ -46,16 +45,6 @@ export default function mount (
4645
// Workaround for Vue < 2.5
4746
vm._staticTrees = []
4847

49-
if (options.scopedSlots) {
50-
addScopedSlots(vm, options.scopedSlots)
51-
52-
if (mergedOptions.sync) {
53-
vm._watcher.sync = true
54-
}
55-
56-
vm.$forceUpdate()
57-
}
58-
5948
const componentsWithError = findAllVueComponentsFromVm(vm).filter(
6049
c => c._error
6150
)

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

+42
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,48 @@ describeWithShallowAndMount('scopedSlots', mountingMethod => {
1313
window = windowSave // eslint-disable-line no-native-reassign
1414
})
1515

16+
itDoNotRunIf(
17+
vueVersion < 2.5 || isRunningPhantomJS,
18+
'mounts component scoped slots',
19+
() => {
20+
const wrapperDefault = mountingMethod(
21+
{
22+
render: function () {
23+
return this.$scopedSlots.default({
24+
index: 1,
25+
item: 'foo'
26+
})
27+
}
28+
},
29+
{
30+
scopedSlots: {
31+
default:
32+
'<p slot-scope="{ index, item }">{{index}},{{item}}</p>'
33+
}
34+
}
35+
)
36+
expect(wrapperDefault.html()).to.equal('<p>1,foo</p>')
37+
38+
const wrapperFoo = mountingMethod(
39+
{
40+
render: function () {
41+
return this.$scopedSlots.foo({
42+
index: 1,
43+
item: 'foo'
44+
})
45+
}
46+
},
47+
{
48+
scopedSlots: {
49+
foo:
50+
'<p slot-scope="{ index, item }">{{index}},{{item}}</p>'
51+
}
52+
}
53+
)
54+
expect(wrapperFoo.html()).to.equal('<p>1,foo</p>')
55+
}
56+
)
57+
1658
itDoNotRunIf(
1759
vueVersion < 2.5 || isRunningPhantomJS,
1860
'mounts component scoped slots',

0 commit comments

Comments
 (0)