Export types for plugins
hirasso opened this issue · comments
Describe the problem
While porting preload-plugin to TypeScript, I noticed that the return type of the preload
method is Promise<PageData>
:
To be able to properly augment the method in swup
, the type PageData
needs to be made exported from swup
:
declare module 'swup' {
export class Swup {
preload: (url: string) => Promise<PageData>; // will throw right now since PageData is not exported by swup
}
}
Or should the method actually return CacheData
instead? Or is there an even simpler solution? :)
Yes, let's export it as well. Technically, it's probably PageData
, not CacheData
, as we return the result of the fetch before any users or plugins can augment the CacheData
object.
In the meantime, ReturnType<Swup['fetchPage']>
should work as the return type and resolve to Promise<PageData>
.
Interesting! Would you prefer exporting PageData
over using ReturnType<Swup['fetchPage']>
? I actually quite like this, as it will make sure the method will throw as soon as the implementation changes in swup. There are others, some of which can be typed using ReturnType
, for example the destroy
methods returned from delegate-it
:
mouseEnterDelegate?: ReturnType<Swup["delegateEvent"]>;
I wasn't able to correctly type the delegate handlers and events, though:
onMouseEnter(event) {}
should be typed like this:
onMouseEnter: DelegateEventHandler = (event: DelegateEvent<MouseEvent, Element>) => {}
DelegateEvent
is also needed for typing the event
in the link:hover
hook:
export interface HookDefinitions {
'link:hover': { el: HTMLAnchorElement, event: DelegateEvent }, // Cannot find name 'DelegateEvent'
'page:preload': { page: ReturnType<Swup['fetchPage']> }
}
I'm a bit confused now 😄 I'll open a PR against swup for exporting the types and you tell me which should be exported and which shouldn't.
I'd go with exporting as many types as possible. Won't hurt 🤠 The ReturnType<>
thing works but feels a bit hacky to me and is less readable. PageType
, DelegateEvent
and DelegateEventUnsubscribe
will come in handy in lots of cases. Let's brainstorm which other ones we might need. HookArguments
?
There's two things wrong with ReturnType<Swup['fetchPage']>
in my opinion:
fetchPage
returns aPromise<PageData>
whereas thepage:preload
returns an actualPageData
object- It looks rather cryptic in autocompletion. As a user, I guess I'd prefer
{ page: PageData }
for clarity
Great! Let's export the types then 👍
I'm trying to augment the hooks "link:hover" and "page:preload" from Preload Plugin now:
declare module 'swup' {
export interface HookDefinitions {
'link:hover': { el: HTMLAnchorElement; event: DelegateEvent };
'page:preload': { page: PageData };
}
}
When I try to use the event in my test project, I need to use this:
const onHoverLink: Handler<"link:hover"> = (visit, { el, event }) => {
console.log('link:hover:', el)
}
swup.hooks.on("link:hover", onHoverLink);
In this case, the visit
object doesn't make much sense. It would be great if we could simplify the callback by modifying the Handler
type so that it doesn't always need visit
and instead spreads the second args
argument for selected hook types, so that we can do:
const onHoverLink: Handler<"link:hover"> = (el, event) => {
console.log('link:hover:', el);
}
I think it‘ll get confusing if 10 out of 12 hooks have the args as a second argument object, and two others have their arguments spread into the handlers. We might need to spell this out very explicitly in the docs — the visit object for these two hooks is most likely useless 🥴 Or we actually find a way of resetting the visit object to some sort of interim visit object, that is somehow marked as stale or inactive?