Repository: vuejs/vue
Pull request: fix(types): allow scoped slots to return a single VNode

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?** (check at least one)

- [x] Bugfix
- [ ] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [x] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [x] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [x] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [x] New/updated tests are included

If adding a **new feature**, the PR's description includes:
- [ ] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

**Other information:**

Scoped slots are normalised to `VNode[]`, but they should still be able to return a bare string or VNode when used in a render function. 
https://codepen.io/anon/pen/BMXvme?editors=1010

Changed files:
filename: types/test/options-test.ts
patch: @@ -323,6 +323,10 @@ Vue.component('component-with-scoped-slot', {
           item: (props: ScopedSlotProps) => [h('span', [props.msg])]
         }
       }),
+      h('child', [
+        // return single VNode (will be normalized to an array)
+        (props: ScopedSlotProps) => h('span', [props.msg])
+      ]),
       h('child', {
         // Passing down all slots from parent
         scopedSlots: this.$scopedSlots

filename: types/vnode.d.ts
patch: @@ -1,7 +1,11 @@
 import { Vue } from "./vue";
 
+export type ScopedSlot = (props: any) => ScopedSlotReturnValue;
+type ScopedSlotReturnValue = VNode | string | boolean | null | undefined | ScopedSlotReturnArray;
+interface ScopedSlotReturnArray extends Array<ScopedSlotReturnValue> {}
+
 // Scoped slots are guaranteed to return Array of VNodes starting in 2.6
-export type ScopedSlot = (props: any) => ScopedSlotChildren;
+export type NormalizedScopedSlot = (props: any) => ScopedSlotChildren;
 export type ScopedSlotChildren = VNode[] | undefined;
 
 // Relaxed type compatible with $createElement

filename: types/vue.d.ts
patch: @@ -12,7 +12,7 @@ import {
   ThisTypedComponentOptionsWithRecordProps,
   WatchOptions,
 } from "./options";
-import { VNode, VNodeData, VNodeChildren, ScopedSlot } from "./vnode";
+import { VNode, VNodeData, VNodeChildren, ScopedSlot, NormalizedScopedSlot } from "./vnode";
 import { PluginFunction, PluginObject } from "./plugin";
 
 export interface CreateElement {
@@ -28,7 +28,7 @@ export interface Vue {
   readonly $children: Vue[];
   readonly $refs: { [key: string]: Vue | Element | Vue[] | Element[] };
   readonly $slots: { [key: string]: VNode[] | undefined };
-  readonly $scopedSlots: { [key: string]: ScopedSlot | undefined };
+  readonly $scopedSlots: { [key: string]: NormalizedScopedSlot | undefined };
   readonly $isServer: boolean;
   readonly $data: Record<string, any>;
   readonly $props: Record<string, any>;