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

Plugin methods #188

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

Plugin methods #188

wants to merge 4 commits into from

Conversation

FelixHenninger
Copy link
Owner

No description provided.

Comment on lines 4 to 11

type PluginEvent = 'plugin:add' | 'plugin:remove'

export class Plugin<C extends Component = Component, E = string> {
async handle(context: C, event: E | PluginEvent, data?: any) {}
export class Plugin<
C extends Component = Component,
E extends string = string,
> {
async handle(context: C, event: E | PluginEvent, ...params: any[]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dynamic dispatching implementation here is clever, I think there's a good way to do it in TS!

First off, it might be easier just to hardcode a handful of empty methods in this base class here and manually trigger them from the handle.

However, if you want to go full TypeScript wizard, it is now possible with Template Literal Types to perform string manipulation on keys within types. To say, define a number of on${Event} methods computed from a union type of events. Here's a tsplayground where I play around with it: https://www.typescriptlang.org/play?#code/C4TwDgpgBAogbhAdsAcgQwLbQLxQOQBOAronlAD75gERho1mV5IAmeAUO6JLAsgBJpELADYQCAYRFoAzjKi4A3uyiqoAbQDWEEFACWiKAAMA9ogAkiiWjB7gaEXoBeEADzwkqTBAB8AXyMAXQAuKAAKAEoFHyg4Ez0Wdj9OAGNpOV5PAAURIgBzA1cJKAgAD2BWeQAjExMxIQAaWBLyyszkdCwYvQwwMSxkeQ8BIVFxKVl5ZTUoMwAlEkioZRU1ZJmzLJo6GiWVmfW1MxhhPc4DpKggA

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoa, thanks! I had no idea this was possible in TS, this is truly excellent. I'll see if I can incorporate your example, many thanks for that!

Given the limited number of intrinsic string manipulation types it probably makes sense to rename some events, specifically plugin:add to pluginAdd etc., but that strikes me as a good idea anyway; as far as I know that's the only place we use the good ol' backbone.js event syntax. (those were the days!)

@FelixHenninger
Copy link
Owner Author

Hej @jdpigeon , I've attempted to add your template in 06ae4e1 -- if you have a moment, I'd super-appreciate your feedback! Overall, I really like the pattern and think that it could be a good model for components, too. That being said:

  • TS doesn't like that we define a type, make it completely optional, and then don't use any of the methods in the reference/default Plugin implementation. I think that this is minor, and have silenced that error for now.
  • The template literal types don't seem to play well with generics -- it looks like they need a hard-coded set of events they would be able to see, which I think wouldn't work well with the remaining code. Then again, hard-coding a set of method names would have the same effect. I would like to write Plugin<C, E> implements EventHandler<E | PluginEvent, C, any> but TS admonishes that A class can only implement an object type or intersection of object types with statically known members. ts(2422). I tried shuffling around the types a bit, and defining an fully generic interface, but I couldn't get things to work.

If we could make this work, I think it would be awesome, but right now I'm not fully convinced, but also not sure if the fallback is any better. Do you have thoughts?

@FelixHenninger
Copy link
Owner Author

After playing around some more, let me maybe add a more concrete example. Here's what works:

type EventHandlerClass<E, T> = {
  [key in `on${Capitalize<EventName>}`]?: (whatever: T) => void
}

Here's what I would like to do:

type EventHandlerClass<E, T> = {
  [key in `on${Capitalize<E>}`]?: (whatever: T) => void
}

Notice the change in Capitalize<E> vs. Capitalize<EventName>. Playground here

@jdpigeon
Copy link
Contributor

Ahhh, it does appear that there's a limitation to that pattern, and I don't think there's going to be a way around that one.

Is it really important for Plugins to be able to handle new events that go beyond those in the standard Component life cycle (+ plugin:prepare, etc.)? Our custom plugins only ever handle 'prepare' 'run' and 'end'.

If you do want to keep that flexibility open, I don't think it's a bad pattern to allow plugins to receive a generic string event name for the handle function, and still have the defaults used to populate the built-in methods. playground

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

Successfully merging this pull request may close these issues.

None yet

2 participants