Closed dfreeman closed 10 months ago
One thing I did want to note:
Today users need to deal with the boilerplate of adding their container-managed classes to the corresponding registries automatically. With engines and local-lookup, which each introduce their own form of sandboxing for these registries, this management only promises to become more complicated as projects grow and initiatives like Module Unification move forward.
In light of that, we might experiment with generating registry declarations automatically, producing one monolithic generated registry interface for each type that imports and references all discovered implementations of that type.
Because of how computed property installation works in Ember 3.1+, it turns we need to actively move people away from the registry pattern for everything but Ember Data.
It’s still something we should support, assuming we end up supporting “last two LTSes” or something like that, as lots of folks are still going to be in <3.1 and not yet on one or the other of classes and decorators. What’s more, while classes + decorators are viable they’re definitely still not the Ember way to go (even if they kind of are in TS land at this point), and I think we’re broadly agreed that we should support Ember defaults as much as possible, even if we recommend classes and decorators as being better and more reliable as far as the type system goes.
That doesn’t remove the need for this – at all! – it just means that whatever we do should focus primarily on the Ember Data part of that story specifically IMO. And the reason for that is that the Ember Data lookups just aren’t going away: this.store.findAll('potatoes')
is going to be with us for the foreseeable future.
Because of how computed property installation works in Ember 3.1+, it turns we need to actively move people away from the registry pattern for everything but Ember Data.
This is because of the field decorators being invisible to the type system? In a rainbows and unicorns world that has the potential to change if/when decorators move forward, right? Just want to make sure I understand the scenario 🙂
This all sounds really great!
I expect we would provide out-of-the-box behavior to produce type declarations for any templates found.
Especially this one :rocket:
My first impression after a few days of e-c-ts usage is "it works!". Currently the biggest issue I think is a lack of typescript support in templates.
At this point we have solved the “types for templates” problem via Glint, and are intentionally not building anything Ember-specific for other forms of codegen, instead deferring that (appropriately!) to projects like GraphQL Code Generator, native CSS Modules support, etc. Closing this accordingly – thanks for the original thoughts written up here @dfreeman, but even more for solving the most important one of these!
I've been mulling over different approaches to this for a bit, and I'd love to start getting thoughts from the Typed Ember folks and the community in general 🙂
Background
There are a number of cases in a typical Ember app where files other than JS/TS sources produce importable modules in the compiled application. Because TypeScript is unaware of these files, it rejects attempts to import them. An elephant-in-the-room example of this is Handlebars templates, whose
import
statements are@ts-ignore
d in our component blueprint to work around this exact issue.The TypeScript compiler doesn't support any notion of loader plugins or anything like that to directly allow for deriving types from non-JS/TS files, but it does allow for merging 'virtual directories' into a source tree, which is the subject of this RFC.
Proposal
I'd like to propose the following:
types/generated
directory and add it torootDirs
and the appropriate places inpaths
to allow for both absolute and relative resolution of modules theretypes/generated
for use bytsc
and other TypeScript-aware toolingFor technical details on how these three points might work, see Approaches below.
Possible Applications
Templates
As an immediate win, I expect we would provide out-of-the-box behavior to produce type declarations for any templates found. The simplest approach here would likely be to produce a simple branded default export in order for component files to have something to import, e.g.
(Note that the
'htmlbars-inline-precompile'
module seems to be our source of truth for the type of a template at present...)config/environment
One non-obvious (or at least it wasn't immediately obvious to me) option we'd get from this is the ability to produce the type declaration for the
<app-name>/config/environment
module. The contents of this module are generated based on a node file executed at build time, and users are currently responsible for maintaining a one-off declaration file.Instead, we may be able to inspect the shape of the config object during the build and emit a type declaration for it into the generated types directory.
Addons
A number of addons compile arbitrary files down to JS modules for consumption. By exposing external hooks for providing generated types, we empower the community to enable participation in the type system for any addon that does this kind of compilation.
Two examples that are near and dear to my heart:
.graphql
files into an AST representation of the query or mutation they contain. Using a tool like apollo-codegen, it could provide the ability for well-typed queries and mutations, using the GraphQL schema to inform required inputs and the type of the returned query result.Experimentation
Richer Template Types
As mentioned above, I think we'd want to provide simple opaque types for template files out of the box. This opens the door, though, for experimentation outside of ember-cli-typescript itself with different approaches to typing templates, perhaps getting richer guarantees about compatibility between templates, their component implementations, and other templates/helpers available in a project.
Generated Type Registries
Today users need to deal with the boilerplate of adding their container-managed classes to the corresponding registries automatically. With engines and local-lookup, which each introduce their own form of sandboxing for these registries, this management only promises to become more complicated as projects grow and initiatives like Module Unification move forward.
In light of that, we might experiment with generating registry declarations automatically, producing one monolithic generated registry interface for each type that imports and references all discovered implementations of that type.
Approaches
There's one primary technical question that needs to be answered in order to move forward with this: what are the inputs and output of type generation? The fundamental fork in the road I've hit is whether this should all be Broccoli-based or not; there are pros and cons either way.
Broccoli Based
A Broccoli solution for this would likely have the generation hook receive a tree as input and return a tree as output. On the e-c-ts side, we'd then be able to take that output tree and essentially move its contents into place in the generated types folder.
Pros
Cons
ts:precompile
)Not-Broccoli Based
If we take a non-broccoli approach, we have more flexibility in terms of what we provide as inputs and expect as an output, but I suspect we'd still wind up with some kind of "read from this directory, and write your results over here" approach.
Pros
ts:precompile
, ensuring generated types are always up to date when publishing addonsCons
app
oraddon
tree actually lives atapp/**
oraddon/**
and that the host isn't doing anything weird on the Broccoli side to add files thereOpen Questions
app
, once fortests
, etc? (This is how ember-cli manages a number of similar APIs)