Skip to content

Simplify resolution and support plain function helpers in GlimmerX #54

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

Merged
merged 8 commits into from
Mar 6, 2021

Conversation

dfreeman
Copy link
Member

@dfreeman dfreeman commented Mar 5, 2021

Overview

This change includes the necessary updates to our invokable resolution process to support treating plain functions as helpers in GlimmerX, ultimately simplifying baseline resolution down to two cases in the process.

Details

The key change is that we now never have free-floating function types being passed around in their Glint-internal format. Instead, invokables are always wrapped as wither Invokable<T> or new (...) => Invokable<T>, depending on the use case.

The comment laid out in packages/template/-private/resolution.d.ts goes into further detail, but standalone Invokable<T> format allows us to resolve complex hand-declared items like fn without losing type fidelity, while new () => Invokable<T> is a natural fit for things like component classes that we already traffic heavily in.

The new version also allows for the preservation of type parameters on the return side of functions that produce invokables, like helper() and {{component}}. Previously we were using regular functions to support this case, which was what led to the difficulty of supporting plain functions.

Notes

This notably breaks fn and on for GlimmerX, which are now temporarily declared as fn2 and on2 to avoid clashing with the built-in definitions of fn and on from GlimmerX itself. Previously we were able to get by with declaration merging, but the way we treat these values is now sufficiently diverged from the baseline definitions that that's no longer possible.

This is already an issue we've been skirting as outlined in #25, and at this point I think the simplest path forward for the time being is to expose modules from the environment packages that directly reexport the same runtime values but with glint-compatible type declarations.

This will also give us the freedom to experiment with alternative form factors for e.g. component type parameters in a way that consumers can try out today rather than having to roll out breaking changes upstream first.

@dfreeman dfreeman added the enhancement New feature or request label Mar 5, 2021
@dfreeman dfreeman merged commit ca65145 into master Mar 6, 2021
@dfreeman dfreeman deleted the simplify-resolution branch March 6, 2021 15:13
expectTypeOf(resolveOrReturn({} as never)).toEqualTypeOf<any>();
expectTypeOf(resolveOrReturn({} as never)).toEqualTypeOf<never>();
Copy link
Member

Choose a reason for hiding this comment

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

Well, this is a nice win.

Comment on lines +20 to +26
on: Invokable<
<T extends keyof HTMLElementEventMap>(
args: NoNamedArgs,
event: T,
callback: (event: HTMLElementEventMap[T]) => void
) => CreatesModifier
>;
Copy link
Member

Choose a reason for hiding this comment

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

Question for my illumination: how would we wire this up to a set of allowed events for a given DOM element (later, obviously; was just thinking about it when reading the change here)?

Copy link
Member Author

Choose a reason for hiding this comment

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

As far as I know TS's own DOM lib doesn't track that information, and with modifiers as generic as they are, I'm not sure if we could express that constraint in a way that would produce useful autosuggest for users even if the types were available.

For the slightly more general question of enforcing that modifiers are only applied to elements they'll work with, the way things are currently set up I think we'd wind up with something like:

function modifier<El extends Element, Positional extends unknown[], Named>(
  f: (element: El, positional: Positional, named: Named): void | () => void
): new () => Invokable<(args: Named, ...positional: Positional) => CreatesModifier<Element>>

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

Successfully merging this pull request may close these issues.

2 participants