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

Template Tag In Routes #1046

Merged
merged 8 commits into from
Nov 22, 2024
Merged

Template Tag In Routes #1046

merged 8 commits into from
Nov 22, 2024

Conversation

ef4
Copy link
Contributor

@ef4 ef4 commented Oct 4, 2024

Propose Use Template Tag in Routes

Rendered

Summary

This pull request is proposing a new RFC.

To succeed, it will need to pass into the Exploring Stage), followed by the Accepted Stage.

A Proposed or Exploring RFC may also move to the Closed Stage if it is withdrawn by the author or if it is rejected by the Ember team. This requires an "FCP to Close" period.

An FCP is required before merging this PR to advance to Accepted.

Upon merging this PR, automation will open a draft PR for this RFC to move to the Ready for Released Stage.

Exploring Stage Description

This stage is entered when the Ember team believes the concept described in the RFC should be pursued, but the RFC may still need some more work, discussion, answers to open questions, and/or a champion before it can move to the next stage.

An RFC is moved into Exploring with consensus of the relevant teams. The relevant team expects to spend time helping to refine the proposal. The RFC remains a PR and will have an Exploring label applied.

An Exploring RFC that is successfully completed can move to Accepted with an FCP is required as in the existing process. It may also be moved to Closed with an FCP.

Accepted Stage Description

To move into the "accepted stage" the RFC must have complete prose and have successfully passed through an "FCP to Accept" period in which the community has weighed in and consensus has been achieved on the direction. The relevant teams believe that the proposal is well-specified and ready for implementation. The RFC has a champion within one of the relevant teams.

If there are unanswered questions, we have outlined them and expect that they will be answered before Ready for Release.

When the RFC is accepted, the PR will be merged, and automation will open a new PR to move the RFC to the Ready for Release stage. That PR should be used to track implementation progress and gain consensus to move to the next stage.

Checklist to move to Exploring

  • The team believes the concepts described in the RFC should be pursued.
  • The label S-Proposed is removed from the PR and the label S-Exploring is added.
  • The Ember team is willing to work on the proposal to get it to Accepted

Checklist to move to Accepted

  • This PR has had the Final Comment Period label has been added to start the FCP
  • The RFC is announced in #news-and-announcements in the Ember Discord.
  • The RFC has complete prose, is well-specified and ready for implementation.
    • All sections of the RFC are filled out.
    • Any unanswered questions are outlined and expected to be answered before Ready for Release.
    • "How we teach this?" is sufficiently filled out.
  • The RFC has a champion within one of the relevant teams.
  • The RFC has consensus after the FCP period.

@github-actions github-actions bot added the S-Proposed In the Proposed Stage label Oct 4, 2024
@ef4 ef4 force-pushed the template-tag-routes branch from 7738818 to 51c419b Compare October 4, 2024 16:52
Copy link
Contributor

@NullVoxPopuli NullVoxPopuli left a comment

Choose a reason for hiding this comment

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

:shipit:


### ember-route-template addon

This RFC replaces the [ember-route-template](https://github.com/discourse/ember-route-template) addon. If you're already using it, it would continue to work without breaking, but you can simply delete all the calls to its `RouteTemplate` function and remove it. The popularity of that addon among teams who are already adopting Template Tag is an argument in favor of this RFC.
Copy link
Contributor

Choose a reason for hiding this comment

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

+100 and thanks for bringing this into Ember directly!

@chancancode
Copy link
Member

Broadly 👍🏼

I think we should also:

  1. add an optional feature that changes the meaning of bare .hbs files in the route templates folder to mean a template only component (as opposed to a component-ish but with controller as this – you can still access it easy enough with @controller)
  2. default to disabled with a deprecation to set the flag one way or the other
  3. eventually deprecate disabling the feature

This is similar to the application template wrapper optional feature/transition. For most apps, the recommendation shouldn't be just setting the feature to disabled, but to actually convert the templates to components – just change this to @controller and keep the .hbs extension (going to .gjs would require analyzing imports etc and is better left to a dedicated codemod). @ember/optional-feature has some rudimentary codemod capabilities that is probably just good enough to take care of this (assuming no implicit this, etc).

The motivation behind this is to more strongly align with the component model everywhere else. In the component folder, if you have a bare .hbs file these days, it just means a template-only component. It would be quite confusing for it mean a wildly different thing in the route template folder. Plus, as it is implemented right now – if you add a .js file next to the .hbs file, because of build-time co-location, it will also turn it into a component.

@NullVoxPopuli
Copy link
Contributor

NullVoxPopuli commented Oct 4, 2024

I don't think we should touch the existing hbs behavior, even with a flag -- benefit too little

@ef4 ef4 added the S-Exploring In the Exploring RFC Stage label Oct 4, 2024
@ef4
Copy link
Contributor Author

ef4 commented Oct 4, 2024

add an optional feature that changes the meaning of bare .hbs files in the route templates folder

I think the hard part about this is that there are quite a lot of places that have to make assumptions about which folders are allowed to do template colocation. We're likely to be chasing down bugs for a while. A partial list:

  • embroider
  • ember-auto-import
  • ember-cli-htmlbars
  • glint
  • ember-template-lint

@chancancode
Copy link
Member

I think the hard part about this is that there are quite a lot of places that have to make assumptions about which folders are allowed to do template colocation. We're likely to be chasing down bugs for a while. A partial list:

I guess I was assuming that some/all of these tools don't care about folders (some may not even have the concept about folders), and blindly assumes adjacent .js/.hbs = component. In my mind that would be the simpler thing to do (considering pods layout also), and arguably the intention of the co-location RFC, but empirically you may be right that they do care somewhat.

@chancancode
Copy link
Member

Also, I was mostly coming from thinking about the implementation in ember-source, we have access to the flag at runtime and can implement the proposed behavior without build tool support. But I do think you are correct that we need to consider whether it will work out in language servers, etc.

@ef4
Copy link
Contributor Author

ef4 commented Oct 4, 2024

Yeah lots of stuff actually blows up if you blindly assume colocation. The most problematic case is not when both js and hbs are present, but when only hbs is.

Also, I was mostly coming from thinking about the implementation in ember-source, we have access to the flag at runtime and can implement the proposed behavior without build tool support.

This would be a radical departure from how it works today and I would not be in favor. Template colocation is an entirely build-time phenomenon today. ember-source knows nothing about it.

@chancancode
Copy link
Member

Also, I was mostly coming from thinking about the implementation in ember-source, we have access to the flag at runtime and can implement the proposed behavior without build tool support.

This would be a radical departure from how it works today and I would not be in favor. Template colocation is an entirely build-time phenomenon today. ember-source knows nothing about it.

I will be more precise about what I meant:

My assumption (apparently not entirely accurate) was that the current build tools more-or-less blindly see co-located pairs and smoosh them down to a single js module, but otherwise leave single hbs files alone. Then, at runtime, if ember-source encounters a template module it'll, at runtime, convert it to a template-only component.

I am pretty sure this was at one point how it had to work, since that behavior was an optional feature for transitional purposes (similar to here), but perhaps that has changed since the flag has been removed. We still seem to have kept the code around in ember-source though. Not sure how that is managed in the build tool side, maybe they also stopped supporting older versions with the flag?

Anyway I didn't mean to say that we shouldn't do this in build tools. In fact I was assuming that's what we already do (when there is a pair). What I mean is that if the build tool didn't take care of the lone hbs files (and my assumption was that they don't today, thus requiring an upgrade), we can still make it work at runtime without hard-requiring build-tool upgrades which we typically don't do.


In any case, I can't really say that much about how hard/easy it is to implement across the ecosystem, but I do think it is an attractive proposition that should be seriously considered, and if rejected, the drawbacks noted in the RFC.

If we do this, then we have a consistent behavior across the board – .hbs means template-only components everywhere, .js/.ts+.hbs works everywhere, and .gjs/.gts works everywhere as well. We also entirely eliminate the concept of standalone templates in the happy path from a teaching perspective. Ultimately, this should also make tooling easier to implement, but I agree there may be some complexity in the meantime if they need to support both.

The drawbacks of not doing this is a bunch of odd inconsistencies – as mentioned the divergent behavior between what lone .hbs file means is one thing, but also, if the build tools doesn't do what I assumed, then we also have to define what it means when you have a .js+.hbs pair in this context, at least making it an error.

Ultimately, I think this fits so well into the new programming model that it's only natural for someone to try the usual patterns in the templates folder, and the failure mode is quite surprising (and subtle).

Keeping these divergent behavior also complicate things for tools in the long run, so it's ultimately a balancing act. I think either way we are going to be chasing down bugs for a while – because of the odd behavior of route templates the "natural" handling wouldn't work for those (e.g. if you are writing a new codemod, if you just write one set of behavior it likely won't work for route templates). This basically gives us a path to get off that weird behavior by running a one time codemod and be done with it, and new tools can require to be already done and not have to worry about the divergent behavior. But I can be convinced that we think we already hunted down most bugs in that direction and would rather leave it alone.


Because the actual difference we are talking about is very small, another alternative to explore would be to make it work both ways with deprecation:

  1. {{@controller}} always work
  2. this refers to the instance if it's a class-based component
  3. Otherwise, this refers to the controller but with a deprecation

This eliminates the need for a flag, and you can always treat route templates as components. It's just that if you happen to have legacy route templates that refers to this we won't break it yet.

I haven't thought that much about how it would be implemented on the ember-source side, but I think if we are willing to put up with some dirty dark magic for a little bit (hidden inside ember-source/glimmer-vm), it can be done regardless of what the build tool does.

@chancancode
Copy link
Member

Put it a different way: the proposal is to stop treating the route templates folder as special in any way.

In the abstract, that should be a attractive proposition, both for learning and for implementation reasons, it also eliminates another place where file placement is significant (and so you/tools have to care about it)

The obstacles seem to be that currently tools do have to sometimes special case it, and is already implanted to behave that way.

If we can, I think it’s worth exploring in the direction of what can we do here to make it possible to opt out of those special treatment. It does take some work to update the existing implementation, but it would be in the direction of where we want to end up anyway.

@kategengler
Copy link
Member

I think making @controller work in route templates and potentially deprecating the use of this in .hbs route templates is a potentially helpful step for migration.

Some things to remember come implementation time:

  • The text on the component needs updating
  • ember-template-imports is not yet in the blueprint

@bwbuchanan
Copy link

I'm probably missing a bunch of context here, but why couldn't a Route specify the template instead of putting it in a separate .hbs or .gts file?

e.g.

import Route from 'component-route';

interface HelloModel {
  world: string;
}

export default class Hello extends Route<HelloModel> {
  <template>Hello {{@model.world}}!</template>

  override model(): HelloModel {
    return { world: 'Earth' };
  }
}

I have this almost working using something similar to the code below.

// component-route/index.ts

import { getComponentTemplate } from '@ember/component';
import { getOwner } from '@ember/owner';
import Route from '@ember/routing/route';

export default class ComponentRoute<Model = unknown> extends Route<Model> {
  override _setRouteName(name: string): void {
    super._setRouteName(name);
    const template = getComponentTemplate(this.constructor);
    if (template != undefined) {
      getOwner(this)?.register(`template:${name}`, template);
    }
  }
}

The only problem is that this in the route template references an empty controller instead of the route instance, so you can't do the below without an intermediate component:

...

goHome() {
  this.router.transitionTo('index');
}

<template>
    <button {{on 'click' this.goHome}}>Go Home</button>
</template>

@ef4
Copy link
Contributor Author

ef4 commented Oct 10, 2024

why couldn't a Route specify the template instead of putting it in a separate .hbs or .gts file?

That would be an example of what I meant by:

A "Route Manager" RFC would allow the creation of new Route implementations that could have their own opinions about how to route to GJS files. This RFC does not preclude that other work from also happening.

I definitely want us to do that too. It's just demonstrably a lot more complex, which is why it isn't done yet. My intent with this RFC is to do a thing that we can ship immediately by targeting one very specific spot in the implementation.

An example like the one you showed would require a new Route base class. But if we commit to a new Route base class, we're really going to want it to solve not just the problem of how to route to GTS, but also the problems of having the correct lifetime, fully eliminating controllers (which means designing better query params), allowing data-loading hooks to run in parallel, eliminating confusing old behaviors around parameter serialization, and ideally also providing strong typescript types for routes. All while correctly inter-operating with today's Route and its quirky timing and transition behaviors, since people can't be expected to convert all their routes simultaneously.

Also, the designs people tend to intuitively reach for here all make the problem of route-based code-splitting worse by putting the data-fetching code and the component hierarchy in the same module, where they can't be split apart without JS-spec-violating behaviors. I am still hoping our design will allow the data for a route and the component hierarchy for a route to load in parallel, but that is a point where reasonable people can disagree (and hence why "Route Manager" would be the first stop, since like Component Manager it would let people do their own implementations on stable public API to demonstrate how they think routing should work).

@ef4
Copy link
Contributor Author

ef4 commented Oct 10, 2024

It does take some work to update the existing implementation, but it would be in the direction of where we want to end up anyway.

But it's not where we want to end up. We want to end up with no more .hbs files!

I really, really do not want to spend time going around the ecosystem conditionally changing how .hbs files are interpreted. They are a legacy codepath. They should be kept as stable as possible.

We still seem to have kept the code around in ember-source though

That code doesn't do component template colocation. It can't. It only covers the case where app/templates/components/thing.hbs exists and has no corresponding app/components/thing.js.

The ember-resolver stores modules without file extensions. It cannot distinguish app/components/thing.js from app/components/thing.hbs. That is why colocation has always been a build-time feature. The resolver only ever sees the JS modules, the build-tooling has taken care of synthesizing it out of one or both of the authored files.

@Panman82
Copy link

While I think this is a good step, I also think this is getting mixed-in with interest for other router improvements. Last I heard there was work taking place for Polaris Edition, but perhaps the router stuff stalled.?. If there was a place to see what was planned for that, it would help to see how this RFC will lead into other router improvements.

@ef4 ef4 removed the S-Proposed In the Proposed Stage label Oct 18, 2024
@kategengler
Copy link
Member

While I think this is a good step, I also think this is getting mixed-in with interest for other router improvements. Last I heard there was work taking place for Polaris Edition, but perhaps the router stuff stalled.?. If there was a place to see what was planned for that, it would help to see how this RFC will lead into other router improvements.

While we'd ideally have at least a Route Manager RFC for Polaris, for now, this RFC is the "router stuff" for Polaris. There is a lot of things we could do but for the most part our efforts are elsewhere and this is a small change that will enable a more coherent story.

This came about while I was pairing with @ef4 on how to update the guides for Template tag. It is incredibly weird to explain .hbs for some things and .g(j|t)s for something else. Ed mentioned that he uses and recommends discourse/ember-route-template. When core team members are building apps and recommending different paths than our guides follow, it is a giant flashing sign that we need to do something to bring those changes to users following the documentation.

@NullVoxPopuli
Copy link
Contributor

NullVoxPopuli commented Oct 18, 2024

With this change,

  • no hbs is needed ever
  • we can use @glint/*@unstable (which currently doesn't support split-file components, because it's hard) (this version of Glint also solves a lot of folks (valid) complaints about Glint ergo)

🎉

@ef4
Copy link
Contributor Author

ef4 commented Oct 25, 2024

Gathering feedback from comments and RFC review meeting discussion:

  • making @controller work in all route templates is worth adding to smooth the difference
  • we could consider deprecating this in even hbs route templates
  • elaborate on the alternative possibility of making even hbs files in the templates directory work as template-only components, with detail on why that is expensive to implement and might not be a priority.

@ef4
Copy link
Contributor Author

ef4 commented Nov 7, 2024

After discussion with the spec meeting, our consensus was against destabilizing anything about existing hbs route files.

All the existing learning materials, community knowledge, and tooling can remain exactly as it is. The gjs vs hbs distinction is a clean and clear break for where the new behavior goes, and no new behaviors go into hbs.

@Panman82
Copy link

Panman82 commented Nov 8, 2024

@ef4 sorry, it's not clear to me what you're getting at. Are you saying this RFC will be "final to closed"? Or part of what is proposed will not go through?

@ef4
Copy link
Contributor Author

ef4 commented Nov 8, 2024

No, I'm saying this RFC is good as written. There were suggestions to expand it to also change the behavior of hbs route templates, and I'm saying I don't want to do that. I want to just introduce gjs route templates without changing anything about hbs.

@NullVoxPopuli
Copy link
Contributor

In short:

  • hbs has 0 risk of anything going wrong (as its not changing)
  • gjs is how you use the new feature 🎉

@apellerano-pw
Copy link

I'm clear on the desire to have a way off hbs asap, and sympathetic to how this pulls the sweater thread that leads to a complete Routing Overhaul RFC.

It wasn't obvious to me when reading the template-only example that I was reading a glimmer component. And I was genuinely shocked when I saw the option to colocate js by 'enhancing' the route template to a glimmer component.

I feel that this 'enhanceability' is exactly what starts pulling the sweater thread. This RFC is demonstrating something nearly like routable components, which is how you get questions like "what's the point of controllers?" and "who owns query params?".

At this stage, if the goal is to really, surgically, just ditch hbs; I think these route templates should be template-only and not offer any 'enhanceability' to a glimmer component. That enhancement pathway opens the door too wide, and will make it difficult to reel in the possibility space when the Routing Overhaul RFC is finally describable.

I think the introduction of @controller is a brand new Ember-ism for folks to learn, with unclear value. Firstly, why do we need both @controller.model and @model? And (critiquing current Ember for a sec) what is the value of @model over this.model? Tightly scoping route templates so that this always means controller, opens no trap doors for future migration.

I think these are the pieces of this RFC that smuggle in further dissatisfaction with Ember Routing, and get ahead of the skis when the goal is "make hbs disappear."

In summary, what if we don't shake up the mental model at all right now, and focus solely on the syntax goal? To me that looks like this:

  • my-route.gjs is locked at template-only
  • this in my-route.gjs still points to controllers/my-route.js
  • there are no special @attributes available in route templates. there is only this
  • if there's secret componentization happening, this has to be an implementation detail of Ember internals, to which there's no comprehension or commitment from user-land

@kategengler
Copy link
Member

I think it would be incredibly confusing for there to ever be a .gjs or .gts file that were not a component and in which this were anything other than the backing component class. @controller isn't any weirder than @model and with template tag the teaching story is coherent -- both @model and @controller are arguments passed to that component -- available in the template as @model and @controller and on the backing class as this.args.model and this.args.controller.

There are questions to answer around future routing, but this change shouldn't impede that exploration nor eventual change. It will enable us to teach only template-tag components for new Ember development in a coherent way even though we still have to explain controllers for query params and certain hooks.

Similar to migrating components to template tag I expect that most existing applications will take a while to do so and do it piecemeal. hbs and route templates will continue to work as they do now. But for new projects and people new to Ember, it will be better to never know a template that isn't a component.

@apellerano-pw
Copy link

apellerano-pw commented Nov 11, 2024

I'm not familiar with the team's overall goals for GJS/GTS, but up until now as a consumer I haven't viewed them as synonymous with Component. I've taken it to be similar to JSX where it signals a syntax extension on top of JS/TS syntax. Agnostic to what the file contents end up representing. I don't think Ember has to buy into the idea that GJS can only contain a Component, if it hasn't already. I think buying in there limits the possibilities for Routing RFCs like this one.

Assuming agnostic file contents: The complexity of what this means in a component.hbs or route.hbs is already present in Ember, and doesn't necessarily have to be addressed right now to move to route.gjs. It definitely stinks, but it's a Routing Overhaul concern more than it's a Sunset HBS concern.

If GJS must always mean Component: I think the sweater thread pull is unavoidable, and this RFC is effectively the first step towards Routing Overhaul. Speaking as an early adopter of GJS, I would avoid using GJS for route templates with this current proposal. I'm worried how un-idiomatic things can become once routes come with both controllers and components. With confusion especially around whether ViewModel should live in route.gjs or controller.js. Every wrong call my team makes, intentional or not, is (best case) a codemod to run or (worst case) a re-write.

@kategengler
Copy link
Member

I think, with this, anything that can be on the component for the route should be -- meaning anything that isn't defining a queryParam or a needed hook on controllers.

@ef4
Copy link
Contributor Author

ef4 commented Nov 12, 2024

I think these route templates should be template-only and not offer any 'enhanceability' to a glimmer component.

I can see two different ways of interpreting this suggestion, so I'll address both.

The first interpretation of template-only is the templateOnly() component manager. It's the thing you get if you use a <template> tag without a class around it, which is also the thing you get if you create app/components/example.hbs without a corresponding app/components/exampe.js.

The problem with trying to limit routing to only templateOnly() components is that Ember is committed to component interoperability. The caller of a component is not supposed to even be able to tell what the implementation of the component is on the inside. So it would be uncanny and artificial to only support one particular component manager in this particular place. If templateOnly() components are valid, so are @glimmer/component and @ember/component and so are any other custom component manager.

(Should you write new @ember/components as your route templates? No, please don't. But it would be perverse for us to throw away our hard-won interoperability principles just to deliberately break this case.)

The second interpretation of "template only" is the way today's route templates works. They are a "bare template" that is not a component. We deliberately designed template tag so it's impossible to use it to obtain a bare template. So bare templates are not a solution to the problem of "how can we avoid teaching people two completely different paradigms for writing their templates?", because you cannot use them with template tag, on purpose.

I'm worried how un-idiomatic things can become once routes come with both controllers and components. With confusion especially around whether ViewModel should live in route.gjs or controller.js. Every wrong call my team makes, intentional or not, is (best case) a codemod to run or (worst case) a re-write.

I agree that avoiding those kind of outcomes is one of our most important goals when we work through an RFC like this.

I believe this RFC reduces the confusion rather than worsening it. Today, you have to teach a complicated decision process:

  1. If you need some local state
    2. If you're in a component, put in on the component
    3. But if you're in a route template, you can decide to put it on the controller or you can decide to make a new component
  2. If you need some Javascript logic
    1. If you're in a component, you can make a getter or a local helper function.
    2. But if you're in a route template, you can decide to make a getter on the controller or a new global helper in a new file.

After this RFC, that teaching simplifies to:

  1. If you need some local state, always put it on the component.
  2. If you need some Javascript logic, you can always make a getter or a local helper function.

@apellerano-pw
Copy link

Riding along with the route+component approach, here are some more thoughts:

  1. What replaces {{outlet}}? {{yield}} is the obvious candidate, but how do Named Blocks fit into the picture? Would be weird to have components that don't work with Named Blocks because they aren't really Components, they're RouteyComponents. Maybe sub-routes always render into the default block? (has-block) is always true in the route-rendered context?

  2. Similarly, once my route template is a component, I might want to re-use that component in other parts of the app as a plain ol' component. @model makes sense for a generic component's API, but @controller seems weird.

  3. All the ViewModel can move seamlessly into component js except for query params. There's gonna be a weird controller file for those, and it's the only thing requiring @controller. I think it's worth pushing thru on that to avoid incoherence... are query params now component args? The query param remapping feature can be replaced by getters.

  4. What do error and loading substates look like? Are these more components with special names? What args do they receive?

@ef4
Copy link
Contributor Author

ef4 commented Nov 12, 2024

All of that is the stuff that's intentionally out of scope for this RFC, because it veers into "redo the router" territory.

What replaces {{outlet}}?

Nothing replaces outlet, you keep using outlet. Outlet is already dynamically scoped, it already works in components, so there's no change.

Would be weird to have components that don't work with Named Blocks

I'm not sure I understand how this relates. We're effectively saying that the router will invoke components with the signature ComponentLike<{ Args: { model: X, controller: Y } }>. That signature doesn't choose to include blocks. This is the same kind of design decision you might make at any level of the component hierarchy. Not every caller provides blocks to invoke.

Notice that we're also not allowing you to customize the args. You can put whatever you want inside @model, but can't choose to replace @model with @person and @comments instead. We're trying to reflect the reality of how routing works. There aren't arbitrarily named arguments in routing and there aren't named blocks.

Similarly, once my route template is a component, I might want to re-use that component in other parts of the app as a plain ol' component. @model makes sense for a generic component's API, but @controller seems weird.

Yes, it's intentionally weird. It reflects the weird reality. The vast majority of new route templates shouldn't use it, and would therefore be portable to elsewhere. If you do use it, you are actively highlighting why your component isn't portable, because it's tied to all this legacy state management.

I think it's worth pushing thru on that to avoid incoherence... are query params now component args? The query param remapping feature can be replaced by getters.

This veers directly into "redesign the entire router" territory, which we're trying to avoid in this RFC. Without doing that first, treating query params special in this RFC would increase incoherence by introducing a new way of doing things that doesn't actually solve the problems we want to solve about query params, so instead of eventually teaching "old query params" and "new query params" we also have a third thing to teach like "old query params but accessed via that other component api".

Query params live on controllers, that's just the truth about them and trying to reflect them elsewhere is fraught because they're mutable and because controllers have unbounded lifetimes.

@chancancode
Copy link
Member

chancancode commented Nov 12, 2024

Sorry I couldn't make those meetings, but between the comment from 3 weeks ago ("elaborate on the alternative possibility...") and the following one ("After discussion with the spec meeting..."), it wasn't clear what was considered regarding my suggestions.

My previous comments were perhaps getting too concrete, and some of the details caused more distractions than helped, so here is the high-level suggestion without getting into the implementation details, summarized in a format suitable for the Alternatives section:

Unifying .hbs as components across the board

Currently, Ember exposes two distinct concepts of "raw templates" and "components" in its API.

Increasingly, raw templates are considered a legacy concept, with proposals like #785 and #931 moving further away from it, and this proposal largely eliminates one of the last reason for why they are still needed. With the Polaris Edition, it seems likely that "raw templates" won't be needed any longer, as every legacy feature should have a modern replacement/migration path (for example, co-location was designed to eliminate the legacy layout pattern in addons), and the concept of "raw templates" and their associated APIs (precompileTemplate, setComponentTemplate, etc) should be on track for deprecation once the community had the chance to absorb the new patterns and APIs.

From users' perspective, in Polaris Edition, components have a pretty straightforward and coherent mental model – components can be authored as a single .gjs/.gts file, which is the now-recommended path, or it can be a pair of .js/.ts+.hbs, and it is possible to omit either of those with reasonable default behavior (omitting .js/.ts means a "template-only" component, with omitting the .hbs gives you a default {{yield}} template for the class).

While this mental model is easy enough to understand, due to the existence of the legacy raw template concept and legacy code that depends on them, this is currently limited to the {app,addon}/components folder. Standalone .hbs files are left as raw templates everywhere else to maintain compatibility with legacy code.

As mentioned above, with this proposal eliminating the last reason for needing raw templates in the mainstream API, the current behavior would be quite undesirable. In Polaris Edition, with explicit imports, while it is desirable for Ember apps to share a common conventional file layout, that should be more of a suggestion and it would be quite surprising/unintuitive for behavior to change depending on the location of the files. The current setup also places unnecessary constraints on any proposed Polaris app layout, and is inconsistent with how the "v2 addon" blueprint works.

This proposal also introduces an inconsistency that further muddies the otherwise coherent component mental model – in which standalone .hbs files in the app/templates folder work differently than the more common standalone .hbs as template-only components. It is also unclear what should the semantics be for "co-located pairs" in the route templates folder.

While keeping the runtime behavior in this proposal (accepting either raw templates or components in the router), an alternative to the build-time behavior proposed here would be to provide a global opt-in to eliminate the convoluted rules and treat all .hbs files and "co-located pairs" the same way across the board – they always emit a single component module regardless of location. In the unlikely case that a raw-template is somehow needed in some low-level code, the precompileTemplate API (and hbs-backtick) remains available to JS to explicitly construct them, until a future proposal holistically consider deprecating/removing them.

Since I am not doing the implementation work, I can't really speak to the cost of implementing my suggestions, but I do think there are tangible benefits and gets us to a more coherent spot for the edition boundary, so if it was considered and rejected for implementation cost reasons, it would be good to have that documented as such.

There was also a suggestion about making @controller available everywhere, which I think is a good idea, and gets it us closer to what I suggested. The disposition of that suggestion is a bit unclear, and if it was lumped into "against destabilizing things", it's not clear why that additive change would destabilize things, and IMO, in a world where we can expect a mix of both during the migration period, it would seem like a gratuitous distinction/foot-gun for that to not work in .hbs.

Regardless of the overall disposition of my suggestions, I also pointed out an obvious hole in my previous comments – this RFC should specify what is supposed to happen when someone inevitably places a .js file adjacent to a .hbs file in the templates folder. At minimum, that should provide some useful feedback/error, but it's not entirely clear who would be responsible for emitting that, and it should be noted that the implementation should be careful to avoid stomping on the now-deprecated (but not yet removed) "non-collocated" components (app/templates/components/**/*.hbs).

To the extent that this RFC proposes special-casing based on file system locations (and I think as written, it kind of is proposing that, if only just inheriting the status quo of location-based component compilation), it probably should specific exactly what those rules are. For example, presumably, this covers addon/templates? Does it exclude pods layout? (it's sort of deprecated in #995 but that seems to only cover components) Should it be understood that .hbs in {app/addon}/templates are special cased, and gets the component semantics everywhere else, or is that that only {app/addon}/components get component semantics but nowhere else?

I get that the RFC as-written is only specifying the runtime behavior, and that the rest is "left to the build tools". But I think it is important to actually specify what we intend for the build tools to do exactly in this RFC, because it's not just embroider that needs to know, but also codemods, linters, glint, etc, all potentially need to have implement the same set of rules correctly to get the right result. Which is the motivation behind my suggestion to take the opportunity to unify things and get rid of all those special casing once and for all.

@ef4
Copy link
Contributor Author

ef4 commented Nov 15, 2024

this RFC should specify what is supposed to happen when someone inevitably places a .js file adjacent to a .hbs file in the templates folder. At minimum, that should provide some useful feedback/error,

This RFC changes nothing about that experience. All the incoherence and downside risk that you're describing is the status quo, and this proposal actually makes it better by explaining what our path is to forever eliminate that incoherence (use GJS instead).

From users' perspective, in Polaris Edition, components have a pretty straightforward and coherent mental model – components can be authored as a single .gjs/.gts file, which is the now-recommended path, or it can be a pair of .js/.ts+.hbs

This is not how I see the plan for How We Teach Polaris. We are not going to teach people to write separate js and hbs. The user's coherent mental model is to use gjs. What docs will remain for hbs are going to be an explanation that it's the older component format and links to the older releases documentation.

To the extent that this RFC proposes special-casing based on file system locations (and I think as written, it kind of is proposing that, if only just inheriting the status quo of location-based component compilation), it probably should specific exactly what those rules are.

Those rules are arcane, implementation-defined, and were implemented with compromises to cover even earlier incoherent things like pods. Asking us to write a spec for them before we can do a feature that makes them irrelevant does not strike me as reasonable.

There was also a suggestion about making @controller available everywhere, which I think is a good idea, and gets it us closer to what I suggested. The disposition of that suggestion is a bit unclear, and if it was lumped into "against destabilizing things", it's not clear why that additive change would destabilize things, and IMO, in a world where we can expect a mix of both during the migration period, it would seem like a gratuitous distinction/foot-gun for that to not work in .hbs.

I can be swayed on this point. Certainly the runtime implementation of @controller isn't hard.

What makes me lukewarm on this, is that I have no interest in spending our precious development resources on updating glint, ember-template-lint, guides, and API docs for this minor change that I would advise people against spending their time on. Changing this to @controller is one of the things that a template tag codemod will do for you automatically.

@ef4
Copy link
Contributor Author

ef4 commented Nov 15, 2024

Maybe a reasonable compromise on @controller is to make it work but make it clear that nobody is being told to refactor to that pattern. So it won't be a footgun if somebody starts attempting to use it in a route they haven't ported to template tag yet, but we're also not telling people they need to learn something new in their old files.

@ef4
Copy link
Contributor Author

ef4 commented Nov 22, 2024

After extensive discussion with @chancancode and @wycats I incorporated edits to record their input and I have confirmation from both of them that we can move past the discussion, since the core feature proposed here has consensus and the other possible changes would stand as their own RFCs.

@ef4 ef4 merged commit 0121ca8 into master Nov 22, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Final Comment Period S-Exploring In the Exploring RFC Stage
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants