]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: exclude legacy slots from $scopedSlots
authorEvan You <yyx990803@gmail.com>
Wed, 5 May 2021 15:06:04 +0000 (11:06 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 5 May 2021 15:06:15 +0000 (11:06 -0400)
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/transforms/vSlot.ts
packages/runtime-core/src/compat/instance.ts
packages/runtime-core/src/compat/renderFn.ts
packages/runtime-core/src/componentRenderContext.ts
packages/vue-compat/__tests__/instance.spec.ts

index 901f00cef156a4483c45ecc5fcfddafb94224966..df8435202097d7ecc6396bafe53147d86d52d862 100644 (file)
@@ -352,6 +352,11 @@ export interface FunctionExpression extends Node {
    * withScopeId() wrapper
    */
   isSlot: boolean
+  /**
+   * __COMPAT__ only, indicates a slot function that should be excluded from
+   * the legacy $scopedSlots instance property.
+   */
+  isNonScopedSlot?: boolean
 }
 
 export interface ConditionalExpression extends Node {
index d2322f4180cb9fa92edade4d5714afcfe57e6ae8..6367743d8bc0a2be322123dc1f9195804ed4685f 100644 (file)
@@ -878,6 +878,9 @@ function genFunctionExpression(
     push(`}`)
   }
   if (isSlot) {
+    if (__COMPAT__ && node.isNonScopedSlot) {
+      push(`, undefined, true`)
+    }
     push(`)`)
   }
 }
index e6387be6c33273845472bf40aad5a9d54fa90345..0c3b28536b62e4995d830c218a213454d3ee1601 100644 (file)
@@ -129,11 +129,6 @@ export function buildSlots(
   const slotsProperties: Property[] = []
   const dynamicSlots: (ConditionalExpression | CallExpression)[] = []
 
-  const buildDefaultSlotProperty = (
-    props: ExpressionNode | undefined,
-    children: TemplateChildNode[]
-  ) => createObjectProperty(`default`, buildSlotFn(props, children, loc))
-
   // If the slot is inside a v-for or another v-slot, force it to be dynamic
   // since it likely uses a scope variable.
   let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
@@ -302,6 +297,17 @@ export function buildSlots(
   }
 
   if (!onComponentSlot) {
+    const buildDefaultSlotProperty = (
+      props: ExpressionNode | undefined,
+      children: TemplateChildNode[]
+    ) => {
+      const fn = buildSlotFn(props, children, loc)
+      if (__COMPAT__) {
+        fn.isNonScopedSlot = true
+      }
+      return createObjectProperty(`default`, fn)
+    }
+
     if (!hasTemplateSlots) {
       // implicit default slot (on component)
       slotsProperties.push(buildDefaultSlotProperty(undefined, children))
index 01c360b4615ea586979c8ee2078ef96deaec8da8..5ed4e19f0ecd36499b452ca6998e1a9eb9973765 100644 (file)
@@ -36,7 +36,7 @@ import {
 } from './renderHelpers'
 import { resolveFilter } from '../helpers/resolveAssets'
 import { resolveMergedOptions } from '../componentOptions'
-import { Slots } from '../componentSlots'
+import { InternalSlots, Slots } from '../componentSlots'
 
 export type LegacyPublicInstance = ComponentPublicInstance &
   LegacyPublicProperties
@@ -103,7 +103,14 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
 
     $scopedSlots: i => {
       assertCompatEnabled(DeprecationTypes.INSTANCE_SCOPED_SLOTS, i)
-      return __DEV__ ? shallowReadonly(i.slots) : i.slots
+      const res: InternalSlots = {}
+      for (const key in i.slots) {
+        const fn = i.slots[key]!
+        if (!(fn as any)._nonScoped) {
+          res[key] = fn
+        }
+      }
+      return res
     },
 
     $on: i => on.bind(null, i),
index af8e78a3379596cb93831e82c931d2b475736300..44df5cbe8410156b3a51d025468a9fdd3f886510 100644 (file)
@@ -281,6 +281,7 @@ function convertLegacySlots(vnode: VNode): VNode {
       for (const key in slots) {
         const slotChildren = slots[key]
         slots[key] = () => slotChildren
+        slots[key]._nonScoped = true
       }
     }
   }
index 6cc232358b536d4171325db2698d95d9122160ce..78557484586b34503c05f917d72b8a07178f3054 100644 (file)
@@ -61,7 +61,8 @@ export const withScopeId = (_id: string) => withCtx
  */
 export function withCtx(
   fn: Function,
-  ctx: ComponentInternalInstance | null = currentRenderingInstance
+  ctx: ComponentInternalInstance | null = currentRenderingInstance,
+  isNonScopedSlot?: boolean // __COMPAT__ only
 ) {
   if (!ctx) return fn
   const renderFnWithContext = (...args: any[]) => {
@@ -83,5 +84,8 @@ export function withCtx(
   // this is used in vnode.ts -> normalizeChildren() to set the slot
   // rendering flag.
   renderFnWithContext._c = true
+  if (__COMPAT__ && isNonScopedSlot) {
+    renderFnWithContext._nonScoped = true
+  }
   return renderFnWithContext
 }
index 74e77225c302816a07a0f46f2b6faf4de3cfa674..3c71e831f4a080094d40aa0646e65650c9d0a17c 100644 (file)
@@ -251,30 +251,56 @@ test('INSTANCE_LISTENERS', () => {
   ).toHaveBeenWarned()
 })
 
-test('INSTANCE_SCOPED_SLOTS', () => {
-  let slots: Slots
-  new Vue({
-    template: `<child v-slot="{ msg }">{{ msg }}</child>`,
-    components: {
-      child: {
-        compatConfig: { RENDER_FUNCTION: false },
-        render() {
-          slots = this.$scopedSlots
+describe('INSTANCE_SCOPED_SLOTS', () => {
+  test('explicit usage', () => {
+    let slots: Slots
+    new Vue({
+      template: `<child v-slot="{ msg }">{{ msg }}</child>`,
+      components: {
+        child: {
+          compatConfig: { RENDER_FUNCTION: false },
+          render() {
+            slots = this.$scopedSlots
+          }
         }
       }
-    }
-  }).$mount()
+    }).$mount()
 
-  expect(slots!.default!({ msg: 'hi' })).toMatchObject([
-    {
-      type: Text,
-      children: 'hi'
-    }
-  ])
+    expect(slots!.default!({ msg: 'hi' })).toMatchObject([
+      {
+        type: Text,
+        children: 'hi'
+      }
+    ])
 
-  expect(
-    deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message
-  ).toHaveBeenWarned()
+    expect(
+      deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message
+    ).toHaveBeenWarned()
+  })
+
+  test('should not include legacy slot usage in $scopedSlots', () => {
+    let normalSlots: Slots
+    let scopedSlots: Slots
+    new Vue({
+      template: `<child><div>default</div></child>`,
+      components: {
+        child: {
+          compatConfig: { RENDER_FUNCTION: false },
+          render() {
+            normalSlots = this.$slots
+            scopedSlots = this.$scopedSlots
+          }
+        }
+      }
+    }).$mount()
+
+    expect('default' in normalSlots!).toBe(true)
+    expect('default' in scopedSlots!).toBe(false)
+
+    expect(
+      deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message
+    ).toHaveBeenWarned()
+  })
 })
 
 test('INSTANCE_ATTR_CLASS_STYLE', () => {