swup / swup

Versatile and extensible page transition library for server-rendered websites 🎉

Home Page:https://swup.js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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>:

image

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 a Promise<PageData> whereas the page:preload returns an actual PageData 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?