Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infer type of slots from $slots #539

Closed
yusufkandemir opened this issue Sep 29, 2021 · 4 comments
Closed

Infer type of slots from $slots #539

yusufkandemir opened this issue Sep 29, 2021 · 4 comments
Labels
enhancement New feature or request

Comments

@yusufkandemir
Copy link
Contributor

The current system analyzes the available slots in different ways and does a good job at that. However, this is not possible for external components(e.g. UI component libraries).

From vuejs/rfcs#192 (comment):

johnsoncodehk (Johnson Chu) on 8 Oct 2020:
But for a third party library like vue-router, I can't read the typescript source code to calculate v-slot types, for this case I need to define additional options for my tool like this: johnsoncodehk/volar#v-slot-type-checking

vuejs/core#4465 allows typing the slots, along with lots of other changes. So, after that gets merged, $slots will basically contain the typed slots.
https://github.com/pikax/vue-next/blob/e201d2f046c46dc1fa26551ce28ac1a322ed1ad9/packages/runtime-core/src/componentPublicInstance.ts#L422

Volar is already inferring the props types from $props:
https://github.com/johnsoncodehk/volar/blob/aa66f9fdc0b1752e58c56d680c942b9d2a3787ec/packages/vscode-vue-languageservice/src/utils/globalDoc.ts#L83-L90

So, in a similar way, $slots can be inferred too, then the current slot discovering system can do its job to enrich the types.

Currently, the UI library/framework authors can ship the constructor types themselves, without depending on current Vue types. An example:
https://github.com/quasarframework/quasar/blob/76c0c841554751a5be743b0302523b63f956ba25/ui/types/ts-helpers.d.ts#L33-L37
(thanks for some imagination from #418 (comment) btw)

So, since this feature is not blocked by vuejs/core#4465 because there are currently ways to provide that type, I think this can be implemented right away! 🚀

@johnsoncodehk johnsoncodehk added the enhancement New feature or request label Sep 29, 2021
@johnsoncodehk
Copy link
Member

Could you try does latest master branch support $slots in expected? You can check #413 for how to run in debug mode.

@yusufkandemir
Copy link
Contributor Author

yusufkandemir commented Sep 29, 2021

@johnsoncodehk
This is the summary of the types I used for testing:

import { VNodeProps, AllowedComponentProps, ComponentCustomProps, Slots as VueSlots } from 'vue';

type PublicProps = VNodeProps & AllowedComponentProps & ComponentCustomProps;

type GlobalComponentConstructor<Props = {}, Slots = VueSlots> = {
  new (): {
    $props: PublicProps & Props
    $slots: Slots
  }
}

interface QTreeSlots {
  'default-header': (scope: { expanded: boolean, ticked: boolean, color: string }) => VNode[]
}

declare module "@vue/runtime-core" {
  interface GlobalComponents {
    QTree: GlobalComponentConstructor<QTreeProps, >
  }
}

this is the result I got:
image

Slots type from Vue:

export declare type Slot = (...args: any[]) => VNode[];

declare type InternalSlots = {
  [name: string]: Slot | undefined;
};

export declare type Slots = Readonly<InternalSlots>;

So, a Slot is basically a function that accepts the scope as the parameter, then returns VNode[]. I believe the returning type is irrelevant in the template context, so it can be ignored. But, it's currently behaving as if the slot function is the slot scope, but actually, it should use the type of the function's parameter.

// Example usage in underlying code that renders the slot 
const scope = {
  expanded: true,
  ticked: false,
  color: 'green'
}

this.$slots['default-header'](scope)
// This shows the type of `$slots` and `Slots` is correct.

So, the expected type of slot is:

type SlotScope = { expanded: boolean, ticked: boolean, color: string }

but it's currently like this:

type SlotScope = (scope: { expanded: boolean, ticked: boolean, color: string }) => VNode[]

This is the current code for inferring the type:

function __VLS_getScriptSlots<T>(t: T): T extends new (...args: any) => { $slots?: infer S } ? (S extends object ? S : {}) : {};

If I am not mistaken, something like this should work:

// `Slot` is from 'vue'
function __VLS_getScriptSlots<T>(t: T): T extends new (...args: any) => { $slots?: infer S } ? (S extends Slot ? Parameters<S>[0] : {}) : {};

Edit: that won't work because it's for the whole object that contains all the slots. 🤦

yusufkandemir added a commit to yusufkandemir/volar that referenced this issue Sep 29, 2021
johnsoncodehk added a commit that referenced this issue Oct 1, 2021
* fix: Pick up the correct slot scope type from `$slots`

See: #539 (comment)

* fix: fixed slots type edge cases

Co-authored-by: johnsoncodehk <[email protected]>
@johnsoncodehk
Copy link
Member

@yusufkandemir fixed, thank you. :)

@yusufkandemir
Copy link
Contributor Author

@johnsoncodehk well, thank you for the fix :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants