Repository: vuejs/vue
Pull request: fix(slot): add a function to return the slot fallback content

slot fallback content should be evaluated only when the parent is not providing it.<!--
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)

- [ ] Bugfix
- [ ] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [ ] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [ ] 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)
- [ ] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [ ] 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:**

fixes #10224

Changed files:
filename: src/compiler/codegen/index.js
patch: @@ -547,7 +547,7 @@ 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}` : ''}`
+  let res = `_t(${slotName}${children ? `,function(){return ${children}}` : ''}`
   const attrs = el.attrs || el.dynamicAttrs
     ? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(attr => ({
         // slot props are camelized

filename: src/core/instance/render-helpers/render-slot.js
patch: @@ -7,26 +7,30 @@ import { extend, warn, isObject } from 'core/util/index'
  */
 export function renderSlot (
   name: string,
-  fallback: ?Array<VNode>,
+  fallbackRender: ?((() => Array<VNode>) | Array<VNode>),
   props: ?Object,
   bindObject: ?Object
 ): ?Array<VNode> {
   const scopedSlotFn = this.$scopedSlots[name]
   let nodes
-  if (scopedSlotFn) { // scoped slot
+  if (scopedSlotFn) {
+    // scoped slot
     props = props || {}
     if (bindObject) {
       if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) {
-        warn(
-          'slot v-bind without argument expects an Object',
-          this
-        )
+        warn('slot v-bind without argument expects an Object', this)
       }
       props = extend(extend({}, bindObject), props)
     }
-    nodes = scopedSlotFn(props) || fallback
+    nodes =
+      scopedSlotFn(props) ||
+      (fallbackRender &&
+        (Array.isArray(fallbackRender) ? fallbackRender : fallbackRender()))
   } else {
-    nodes = this.$slots[name] || fallback
+    nodes =
+      this.$slots[name] ||
+      (fallbackRender &&
+        (Array.isArray(fallbackRender) ? fallbackRender : fallbackRender()))
   }
 
   const target = props && props.slot

filename: test/unit/features/component/component-slot.spec.js
patch: @@ -109,6 +109,47 @@ describe('Component slot', () => {
     expect(child.$el.children[1].textContent).toBe('slot b')
   })
 
+ it('it should work with previous versions of the templates', () => {
+   const Test = {
+      render() {
+        var _vm = this;
+        var _h = _vm.$createElement;
+        var _c = _vm._self._c || vm._h;
+        return _c('div', [_vm._t("default", [_c('p', [_vm._v("slot default")])])], 2)
+      }
+    }
+    let vm = new Vue({
+      template: `<test/>`,
+      components: { Test }
+    }).$mount()
+    expect(vm.$el.textContent).toBe('slot default')
+    vm = new Vue({
+      template: `<test>custom content</test>`,
+      components: { Test }
+    }).$mount()
+    expect(vm.$el.textContent).toBe('custom content')
+  })
+
+  it('fallback content should not be evaluated when the parent is providing it', () => {
+    const test = jasmine.createSpy('test')
+    const vm = new Vue({
+      template: '<test>slot default</test>',
+      components: {
+        test: {
+          template: '<div><slot>{{test()}}</slot></div>',
+          methods: {
+            test () {
+              test()
+              return 'test'
+            }
+          }
+        }
+      }
+    }).$mount()
+    expect(vm.$el.textContent).toBe('slot default')
+    expect(test).not.toHaveBeenCalled()
+  })
+
   it('selector matching multiple elements', () => {
     mount({
       childTemplate: '<div><slot name="t"></slot></div>',

filename: test/unit/modules/compiler/codegen.spec.js
patch: @@ -196,7 +196,7 @@ describe('codegen', () => {
   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",function(){return [_c('div',[_v("hi")])]})],2)}`
     )
   })