-
-
Notifications
You must be signed in to change notification settings - Fork 407
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
RFC: Glimmer component Signature
type
#748
RFC: Glimmer component Signature
type
#748
Conversation
I've been using Glint (and loving it) and didn't realize |
|
I think I prefer |
I should also add that we've been using Glint for a few weeks and I love this approach. I'm fully on board. |
How about |
But seriously, I do agree that |
|
Obviously we can bikeshed all day on this one. 😂 The two things I'd note:
|
Some feedback from a discussion with Framework Core about this on 2021/05/28, with my own commentary added in:
|
Discussion in the 2021-06-25 Framework Core Team meeting: folks are generally happy with where this is at. @chancancode noted that he doesn't care for the name interface SignatureA {
Blocks: [string];
}
interface SignatureB {
Blocks: {
header: [];
body: [string];
}
} to this: interface SignatureA {
Blocks: {
default: {
Params: [string]
}
};
}
interface SignatureB {
Blocks: {
header: {
Params: [];
};
body: {
Params: [string];
};
}
} This would then be "forward-looking" in that it supports future expansion of the idea of I'm leaving this comment as representative of that discussion, not representative of my own opinion here. I expect we'll probably have some further discussion on this thread on that point, though! |
For my part, I disagree with this approach pretty strongly, for two reasons:
I'll start with (2) and then circle back to (1). When reading That leads me to (1): while I think it's good to design in a way that allows us to expand in the future, I do not believe we should design this to have a worse pedagogical experience now for something which may or may not ever happen. What's more, the use of This asymmetry of costs means that I think very strongly that we should not use |
There is a lot of confusion about it, especially, comparing with other frameworks and standards. In templates we have "yield", but we have "has-block" helper, Syntaxis will be different for both. In other frameworks, same behaviour has name "slot". In web API, it's also "slot" - https://developer.mozilla.org/en-US/docs/Web/API/Element/slot It's literally hell for any new developers. Should we simplify overall API surface? (array of params, hashes and such, both on the same time used by 20% of developers, but brings strong overhead across ecosystem) helper(unknown[], Record<string,unknown>) -> helper(...unknown[])
modifier(node, unknown[], Record<string,unknown>) -> modifier(node, ...unknown[]) Alternative version of naming (and default slot remains just 'slot') interface SignatureA {
Slot: [string, number]
}
interface SignatureB {
Slots: {
header: [ ],
body: [ string ]
}
}
interface SignatureC {
Slot: [ number ],
Slots: {
header: [ ],
body: [ string ]
}
} also, I like @chriskrycho reference about interface SignatureA {
yield: [string, number]
}
interface SignatureB {
yields: {
header: [ ],
body: [ string ]
}
}
interface SignatureC {
yield: [ number ],
yields: {
header: [ ],
body: [ string ]
}
} |
@lifeart yeah, we all spent some time thinking about the "slot" nomenclature a while back, and ultimately came down against because the behavior of slots in Vue, Svelte, and the web API is different enough from what named blocks do that it doesn't map in well. I believe there was some discussion of this on the original named blocks RFC as well. In any case, we should make the names used in the types match the names used by the framework, so the only real options are some combination of "blocks" and "yields" and maybe "params" with those. (If we renamed them at the framework level, we would of course also rename them in the types, but that's a different discussion for sure.) |
I think there are two perspectives to it:
Params are a mechnanics to transport data from inside the component to the outside. Also, my direction of reading is an author writes the type signature for the consumers, in the "language" of the consumers. |
Thank you for writing up the Framework Core notes, @chriskrycho! I do think it's worth noting that In the course of working on Glint and on this RFC, I went hunting for prior art as input for both modeling and nomenclature. Among other languages, our blocks system perhaps most closely resembles Ruby's (albeit with some notable distinctions, such as components accepting multiple blocks and having no notion of a block return value). Accordingly, the Ruby typechecker Sorbet seemed a promising place to look, since they similarly needed to account for how to statically describe what kind of block might be passed to a method. However, Sorbet (and Ruby itself) treats blocks and yields as essentially sugar for passing and invoking a callback, while Glimmer blocks occupy an entirely different namespace from component args and can't be invoked in any way other than with a This in itself may provide useful insight, though. Suppose we had a script-y analog to the API for invoking a component in a template, where args are passed via an object literal and a default "block" is a callback. The equivalent to calling this member of the signature interface ScriptyComponentSignature {
Args: object;
Callback: unknown[]
}
class ScriptyComponent<T extends ScriptyComponentSignature> {
constructor(
private args: T['Args'],
private callback: (...params: T['Callback']) => void
) {}
} We can see a hint of where that name might be misaligned above, but it becomes much more explicit when we go to define a component: interface EachComponentSignature<T> {
Args: { items: Array<T> };
Callback: [item: T; index: number]; // 🤔
}
class EachComponent<T> extends ScriptyComponent<EachComponentSignature<T>> {
// ...
}
new EachComponent({ items: ['A', 'B', 'C'] }, (item, index) => {
console.log(`#${index + 1}: ${item}`);
}); Writing a tuple type Drawing this parallel was what led to us adopting the term Similar to using a name like
I pretty strongly agree with both of @chriskrycho's points here. Future extensibility is absolutely an important aspect of API design, but an appropriate narrowly-scoped name still allows a straightforward migration path should we need it in the future without forcing a pedagogical model that requires explaining hypothetical future extensions to make sense today. |
To make one thing clear that came up in the strike team call just now, the specific case that doesn't sit well with me for interface EachSignature<T> {
// ...
Blocks: [item: T; index: string];
}
interface SimpleWrappingSignature {
// ...
Blocks: [];
} On its own I like the In combination with the |
|
||
### `Element` | ||
|
||
The `Element` member of the signature declares what type of DOM element(s), if any, this component may be treated as encapsulating. That is, setting a non-`null` type for this member declares that this component may have HTML attributes applied to it, and that type reflects the type of DOM `Element` object any modifiers applied to the component will receive when they execute. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would be the correct type for a custom element? The class of the custom element?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s correct. Note that the corresponding class is what’s used for regular HTML elements, too, so: HTMLAnchorElement
, for an a
tag, for example.
2. How can I nest content within this component? | ||
- Does it accept a default block? Named blocks? | ||
- In what context will my block content appear in the component's output? | ||
- For any blocks I write, what parameters does the component expose to them? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current proposal does not seem to address all of these questions. The provided type information does not give a hint about the elements allowed within the block in all cases. E.g. if the component yields within a button element, a developer must only use phrasing content, which is not interactive.
In most cases this could be derived from the Element
type. But only the component also applies splat attributes to that element. This may not be desirable in all cases.
Also there is the case, in which a parent element, limits the permitted content of a child element. This is the case for any element, which allows transparent content (e.g. <del>
and <ins>
). In that case permitted content can not be derived from that element alone.
Not sure if that must be addressed by this RFC though. Maybe it is enough to explicitly name this limitation in the RFC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current proposal does not seem to address all of these questions.
It does; the additional very interesting point you raise about children is an additional point. While we could revise the RFC to make it explicit, the answer is implicit in the semantics of templates and unaffected by this proposal: they are simply functions of HTML. This proposal is not trying to specify a way to guarantee that no invalid HTML can be written. It is simply interested in extending the set of things which can be statically known about it for documentation and type checking purposes.
It might be possible to add in a further layer of analysis to accomplish that kind of analysis at a later point, but it is not a current goal for this doc or even for Glint. And it’s worth note that with this proposal and Glint, we are already doing more than any other JS framework. (The one ecosystem I think has taken a swing at the “allowed children” problem is PureScript, though I don’t know if their exploration in that space ended up broadly used even there. It is a very difficult problem because there is no inherent relationship between parent and child elements in HTML syntactically, only semantically.)
Update for folks watching this issue as eagerly as I am: we very briefly discussed this in the today’s Framework Core team meeting and are planning to have it resolved within the next few weeks (hopefully, though of course this being software there are no guarantees, by the end of the month!). I fully expect we’ll come out of that with a design we’re all satisfied with, and ready to move forward. I’ll update again as soon as we get there! |
First, I love this! 😍
What I don't quite get is the reason for this... When I author the signature of a component, I would like
Take the following (intentionally broken) example: export interface NoticeSignature {
Element: 'foo';
Args: 'bar';
/**
* Any default block content will be displayed in the body of the notice.
* This block receives a single `dismiss` parameter, which is an action
* that can be used to dismiss the notice.
*/
Block: {
default: [dismiss: () => void];
};
}
export default class Notice extends Component<NoticeSignature> {
// ...
} If you paste that into the TS playground linked in the RFC, TS wouldn't complain at all. For Had I been able to to write this as We don't need to enforce users doing that, but why are we "actively" preventing it? |
Thanks for the feedback, @simonihmig! The problem with providing a base interface is that
TypeScript doesn't provide completions from a super interface, since the typical use case for one interface to extend another is to add new things rather than refine existing ones.
For the same reason as outlined above, TypeScript has no issue with you adding extra keys that aren't part of the parent interface—after all, that's the point of extending an interface—so you won't get an error if you make a typo like Ultimately, writing |
Oh geez, you are totally right! I was confusing that terribly somehow... 🙈 |
Details ------- Following the design pioneered in emberjs/rfcs#748, introduce a new `Signature` type and use it in defining the types for both function- and class-based modifiers. This allows end users to write, for example: interface PlaySig { Args: { Named: { when: boolean; } } Element: HTMLMediaElement; } export default modifier<PlaySig>((el, _, { when: shouldPlay }) => { if (shouldPlay) { el.play(); } else { el.pause(); } }); This supports both `Named` and `Positional` args, and works equally well with class-based modifiers. It also provides the `Element` type as `Element` by default, and provides internal type utilities as well as one external-facing type utility to make writing the type of `args` when passed to a modifier `constructor` easy to do in terms of the `Signature` which represents the modifier. Users do not *need* to type this form: the form using basic inference continues to work as well: export default modifier(( el: HTMLMediaElement, _: [], { when: shouldPlay }: { when: boolean } ) => { if (shouldPlay) { el.play(); } else { el.pause(); } })); That is: the support for `Signatures` is strictly *new* capability which was not present before. The same kind of signature works with class-based modifiers as well, while retaining backward compatibility with the `Args`-based signature and declaration on the subclass `element` field directly. To support that in a way that does not require users to name their args over and over again, introduce an `ArgsFor` type utility which translates from the signature type to the runtime form: import Modifier, { ArgsFor } from 'ember-modifier'; interface PlaySig { Args: { Named: { when: boolean; }; }; Element: HTMLMediaElement; } class MyModifier extends Modifier<PlaySig> { constructor(owner: unknown, args: ArgsFor<PlaySig>) { super(owner, args); // ... } didReceiveArguments() { const shouldPlay = this.args.named.when; if (shouldPlay) { this.element.play(); } else { this.element.pause(); } } } Additionally, the `modifier` function now returns an opaque `FunctionBasedModifier` type. This mostly exists to provide nice hooks for tooling to hook onto, but it has the added benefit of showing something besides `unknown` in an editor, and that "something" shows the arguments and element specified for the modifier. Supporting changes ------------------ - Update the README to match, and fix a *lot* of long-since outdated documentation along the way. - Loosen type test constraint to support older TS versions The new type signature *is* compatible (as the tests on TS 4.5+ show), but on older versions of TS, it does not check under a type equality check, only under a type matching check. For the purposes of the specific test in question, that's perfectly fine, because the *other* type tests cover inference and resolution correctly.
Details ------- Following the design pioneered in emberjs/rfcs#748, introduce a new `Signature` type and use it in defining the types for both function- and class-based modifiers. This allows end users to write, for example: interface PlaySig { Args: { Named: { when: boolean; } } Element: HTMLMediaElement; } export default modifier<PlaySig>((el, _, { when: shouldPlay }) => { if (shouldPlay) { el.play(); } else { el.pause(); } }); This supports both `Named` and `Positional` args, and works equally well with class-based modifiers. It also provides the `Element` type as `Element` by default, and provides internal type utilities as well as one external-facing type utility to make writing the type of `args` when passed to a modifier `constructor` easy to do in terms of the `Signature` which represents the modifier. Users do not *need* to type this form: the form using basic inference continues to work as well: export default modifier(( el: HTMLMediaElement, _: [], { when: shouldPlay }: { when: boolean } ) => { if (shouldPlay) { el.play(); } else { el.pause(); } })); That is: the support for `Signatures` is strictly *new* capability which was not present before. The same kind of signature works with class-based modifiers as well, while retaining backward compatibility with the `Args`-based signature and declaration on the subclass `element` field directly. To support that in a way that does not require users to name their args over and over again, introduce an `ArgsFor` type utility which translates from the signature type to the runtime form: import Modifier, { ArgsFor } from 'ember-modifier'; interface PlaySig { Args: { Named: { when: boolean; }; }; Element: HTMLMediaElement; } class MyModifier extends Modifier<PlaySig> { constructor(owner: unknown, args: ArgsFor<PlaySig>) { super(owner, args); // ... } didReceiveArguments() { const shouldPlay = this.args.named.when; if (shouldPlay) { this.element.play(); } else { this.element.pause(); } } } Additionally, the `modifier` function now returns an opaque `FunctionBasedModifier` type. This mostly exists to provide nice hooks for tooling to hook onto, but it has the added benefit of showing something besides `unknown` in an editor, and that "something" shows the arguments and element specified for the modifier. Supporting changes ------------------ - Update the README to match, and fix a *lot* of long-since outdated documentation along the way. - Loosen type test constraint to support older TS versions The new type signature *is* compatible (as the tests on TS 4.5+ show), but on older versions of TS, it does not check under a type equality check, only under a type matching check. For the purposes of the specific test in question, that's perfectly fine, because the *other* type tests cover inference and resolution correctly.
Details ------- Following the design pioneered in emberjs/rfcs#748, introduce a new `Signature` type and use it in defining the types for both function- and class-based modifiers. This allows end users to write, for example: interface PlaySig { Args: { Named: { when: boolean; } } Element: HTMLMediaElement; } export default modifier<PlaySig>((el, _, { when: shouldPlay }) => { if (shouldPlay) { el.play(); } else { el.pause(); } }); This supports both `Named` and `Positional` args, and works equally well with class-based modifiers. It also provides the `Element` type as `Element` by default, and provides internal type utilities as well as one external-facing type utility to make writing the type of `args` when passed to a modifier `constructor` easy to do in terms of the `Signature` which represents the modifier. Users do not *need* to type this form: the form using basic inference continues to work as well: export default modifier(( el: HTMLMediaElement, _: [], { when: shouldPlay }: { when: boolean } ) => { if (shouldPlay) { el.play(); } else { el.pause(); } })); That is: the support for `Signatures` is strictly *new* capability which was not present before. The same kind of signature works with class-based modifiers as well, while retaining backward compatibility with the `Args`-based signature and declaration on the subclass `element` field directly. To support that in a way that does not require users to name their args over and over again, introduce an `ArgsFor` type utility which translates from the signature type to the runtime form: import Modifier, { ArgsFor } from 'ember-modifier'; interface PlaySig { Args: { Named: { when: boolean; }; }; Element: HTMLMediaElement; } class MyModifier extends Modifier<PlaySig> { constructor(owner: unknown, args: ArgsFor<PlaySig>) { super(owner, args); // ... } didReceiveArguments() { const shouldPlay = this.args.named.when; if (shouldPlay) { this.element.play(); } else { this.element.pause(); } } } Additionally, the `modifier` function now returns an opaque `FunctionBasedModifier` type. This mostly exists to provide nice hooks for tooling to hook onto, but it has the added benefit of showing something besides `unknown` in an editor, and that "something" shows the arguments and element specified for the modifier. Supporting changes ------------------ - Update the README to match, and fix a *lot* of long-since outdated documentation along the way. - Loosen type test constraint to support older TS versions The new type signature *is* compatible (as the tests on TS 4.5+ show), but on older versions of TS, it does not check under a type equality check, only under a type matching check. For the purposes of the specific test in question, that's perfectly fine, because the *other* type tests cover inference and resolution correctly.
Details ------- Following the design pioneered in emberjs/rfcs#748, introduce a new `Signature` type and use it in defining the types for both function- and class-based modifiers. This allows end users to write, for example: interface PlaySig { Args: { Named: { when: boolean; } } Element: HTMLMediaElement; } export default modifier<PlaySig>((el, _, { when: shouldPlay }) => { if (shouldPlay) { el.play(); } else { el.pause(); } }); This supports both `Named` and `Positional` args, and works equally well with class-based modifiers. It also provides the `Element` type as `Element` by default, and provides internal type utilities as well as one external-facing type utility to make writing the type of `args` when passed to a modifier `constructor` easy to do in terms of the `Signature` which represents the modifier. Users do not *need* to type this form: the form using basic inference continues to work as well: export default modifier(( el: HTMLMediaElement, _: [], { when: shouldPlay }: { when: boolean } ) => { if (shouldPlay) { el.play(); } else { el.pause(); } })); That is: the support for `Signatures` is strictly *new* capability which was not present before. The same kind of signature works with class-based modifiers as well, while retaining backward compatibility with the `Args`-based signature and declaration on the subclass `element` field directly. To support that in a way that does not require users to name their args over and over again, introduce an `ArgsFor` type utility which translates from the signature type to the runtime form: import Modifier, { ArgsFor } from 'ember-modifier'; interface PlaySig { Args: { Named: { when: boolean; }; }; Element: HTMLMediaElement; } class MyModifier extends Modifier<PlaySig> { constructor(owner: unknown, args: ArgsFor<PlaySig>) { super(owner, args); // ... } didReceiveArguments() { const shouldPlay = this.args.named.when; if (shouldPlay) { this.element.play(); } else { this.element.pause(); } } } Additionally, the `modifier` function now returns an opaque `FunctionBasedModifier` type. This mostly exists to provide nice hooks for tooling to hook onto, but it has the added benefit of showing something besides `unknown` in an editor, and that "something" shows the arguments and element specified for the modifier. Supporting changes ------------------ - Update the README to match, and fix a *lot* of long-since outdated documentation along the way. - Loosen type test constraint to support older TS versions The new type signature *is* compatible (as the tests on TS 4.5+ show), but on older versions of TS, it does not check under a type equality check, only under a type matching check. For the purposes of the specific test in question, that's perfectly fine, because the *other* type tests cover inference and resolution correctly.
Details ------- Following the design pioneered in emberjs/rfcs#748, introduce a new `Signature` type and use it in defining the types for both function- and class-based modifiers. This allows end users to write, for example: interface PlaySig { Args: { Named: { when: boolean; } } Element: HTMLMediaElement; } export default modifier<PlaySig>((el, _, { when: shouldPlay }) => { if (shouldPlay) { el.play(); } else { el.pause(); } }); This supports both `Named` and `Positional` args, and works equally well with class-based modifiers. It also provides the `Element` type as `Element` by default, and provides internal type utilities as well as one external-facing type utility to make writing the type of `args` when passed to a modifier `constructor` easy to do in terms of the `Signature` which represents the modifier. Users do not *need* to type this form: the form using basic inference continues to work as well: export default modifier(( el: HTMLMediaElement, _: [], { when: shouldPlay }: { when: boolean } ) => { if (shouldPlay) { el.play(); } else { el.pause(); } })); That is: the support for `Signatures` is strictly *new* capability which was not present before. The same kind of signature works with class-based modifiers as well, while retaining backward compatibility with the `Args`-based signature and declaration on the subclass `element` field directly. To support that in a way that does not require users to name their args over and over again, introduce an `ArgsFor` type utility which translates from the signature type to the runtime form: import Modifier, { ArgsFor } from 'ember-modifier'; interface PlaySig { Args: { Named: { when: boolean; }; }; Element: HTMLMediaElement; } class MyModifier extends Modifier<PlaySig> { constructor(owner: unknown, args: ArgsFor<PlaySig>) { super(owner, args); // ... } didReceiveArguments() { const shouldPlay = this.args.named.when; if (shouldPlay) { this.element.play(); } else { this.element.pause(); } } } Additionally, the `modifier` function now returns an opaque `FunctionBasedModifier` type. This mostly exists to provide nice hooks for tooling to hook onto, but it has the added benefit of showing something besides `unknown` in an editor, and that "something" shows the arguments and element specified for the modifier. Supporting changes ------------------ - Update the README to match, and fix a *lot* of long-since outdated documentation along the way. - Loosen type test constraint to support older TS versions The new type signature *is* compatible (as the tests on TS 4.5+ show), but on older versions of TS, it does not check under a type equality check, only under a type matching check. For the purposes of the specific test in question, that's perfectly fine, because the *other* type tests cover inference and resolution correctly.
Adds backwards-compatible support for component `Signature`s per [Ember RFC #0748][rfc]. The public API of `Component` is now *more* permissive than it was previously, because it changes the type parameter to be an unconstrained generic `<S>` (for "signature") which can accept *either* the existing `Args` types *or* a new `Signature` which includes `Args` and adds `Blocks` and `Element`. [rfc]: emberjs/rfcs#748 The `Args` part of the new signature work exactly like the old args-only type did. The `Blocks` and `Element` parts of a signature are inert from the perspective of TypeScript users who are not yet using [Glint][glint], but Glint users will benefit directly once Glint releases an update which can requires a version of `@glimmer/component` incorporating this change. [glint]: https://github.com/typed-ember/glint Since this change is backwards compatible, we can deprecate the non-`Signature` form at a later time when we are ready for a 2.0 release. To validate these changes, with the relatively complicated type machinery they require under the hood, this also introduces the `expect-type` type testing library, rewrites the existing type tests using it, and introduces new type tests for all supported forms of the `Signature`.
RFC #724 originally intentionally excluded template type checking via Glint from its designation of "official support" for Ember. However, in the time since the RFC was authored, there have been two significant changes: 1. Glint itself has matured significantly, with no known major issues at this time (though plenty of polish still to be done). 2. The *major* design issues in Ember needed to unblock Glint have been resolved: - resolution of template identifiers (components, helpers, and modifiers): RFC #779 - a Glimmer Component type signature which exposes information about the blocks and the target for `...attributes`: RFC #748 Although there remain a number of smaller design questions to resolve, this is sufficient for us to treat type-checked templates as a viable part of our story, and given *viability*, we think this is *necessary*.
Great work getting this over the line, thank you for the awesome discussion here folks! Let's do this!!!! 💪 🎉 💯 |
Adds backwards-compatible support for component `Signature`s per [Ember RFC #0748][rfc]. The public API of `Component` is now *more* permissive than it was previously, because it changes the type parameter to be an unconstrained generic `<S>` (for "signature") which can accept *either* the existing `Args` types *or* a new `Signature` which includes `Args` and adds `Blocks` and `Element`. [rfc]: emberjs/rfcs#748 The `Args` part of the new signature work exactly like the old args-only type did. The `Blocks` and `Element` parts of a signature are inert from the perspective of TypeScript users who are not yet using [Glint][glint], but Glint users will benefit directly once Glint releases an update which can requires a version of `@glimmer/component` incorporating this change. [glint]: https://github.com/typed-ember/glint Since this change is backwards compatible, we can deprecate the non-`Signature` form at a later time when we are ready for a 2.0 release. To validate these changes, with the relatively complicated type machinery they require under the hood, this also introduces the `expect-type` type testing library, rewrites the existing type tests using it, and introduces new type tests for all supported forms of the `Signature`. (cherry picked from commit 967d028)
Adds backwards-compatible support for component `Signature`s per [Ember RFC #0748][rfc]. The public API of `Component` is now *more* permissive than it was previously, because it changes the type parameter to be an unconstrained generic `<S>` (for "signature") which can accept *either* the existing `Args` types *or* a new `Signature` which includes `Args` and adds `Blocks` and `Element`. [rfc]: emberjs/rfcs#748 The `Args` part of the new signature work exactly like the old args-only type did. The `Blocks` and `Element` parts of a signature are inert from the perspective of TypeScript users who are not yet using [Glint][glint], but Glint users will benefit directly once Glint releases an update which can requires a version of `@glimmer/component` incorporating this change. [glint]: https://github.com/typed-ember/glint Since this change is backwards compatible, we can deprecate the non-`Signature` form at a later time when we are ready for a 2.0 release. To validate these changes, with the relatively complicated type machinery they require under the hood, this also introduces the `expect-type` type testing library, rewrites the existing type tests using it, and introduces new type tests for all supported forms of the `Signature`. (cherry picked from commit 967d028)
Super-abridged summary: for TypeScript, this RFC proposes to change
GlimmerComponent
'sArgs
type parameter to aSignature
type that can capture richer information about how a component can be invoked.Rendered text