Closed davismj closed 1 year ago
I like it. Some initial thoughts:
How are query parameters passed to the component? What if a path parameter and query parameter share a name? Does one override or do you pass an array of both values?
I like that the syntax looks more familiar. In a previous version, you used to have some type checking to see if a parameter was a number, id, regex ,etc. Is this coming back?
Do we need both pages
and routes
as keywords? I think that could be confusing. Could we use the same keyword?
How do the file and class name conventions interact. Does one override the other? Or is there a config setting where the developer chooses one or the other?
I will be interested in how your implementation of JWX's app goes to see if there are fundamental differences or the proposals just differ in syntax and conventions.
How are query parameters passed to the component? What if a path parameter and query parameter share a name? Does one override or do you pass an array of both values?
Can you give an example to clarify what you mean?
In a previous version, you used to have some type checking to see if a parameter was a number, id, regex ,etc. Is this coming back?
Yes! This is only about how to register routes with the Router
. This is orthogonal to other router functionality. In any case, the conventional parameters will not be typed.
Do we need both
pages
androutes
as keywords? I think that could be confusing. Could we use the same keyword?
That's a really good point. I typically use 'pages'
as my folder for pages in applications, which is why I used it here; it is illuminating. However, HomePage
didn't feel as illuminating to the convention. What is your suggestion?
How do the file and class name conventions interact. Does one override the other?
Yes, class name > file name. I thought it was implicit in the order of the spec above, but now I can see that it isn't.
Or is there a config setting where the developer chooses one or the other?
TODO: Add router configuration to this spec.
I like being able to get at the route table from DI. That's nice. The configuration of that looks pretty straight forward. Same for the static property and decorator approaches.
For the static property and decorator approaches, for JIT-mode, it seems those will need to be imported and registered somewhere in order for them to add their configuration to the router. I don't see the details of that process here, but there needs to be some way for that data to get into the route table in JIT mode. It can be done automatically in AOT-mode.
For the class name and path conventions, these are the pieces that need to be harmonized with the proposal from @jwx. The way the two of you describe these scenarios is quite different but I think the manifestation of them is very similar. They aren't really that different. Some of what is being described here is probably only possible through AOT since there's no loader abstraction in Aurelia vNext. We could make our vNext router depend on import
as a standard and require a polyfill for that I supposed, and then you could do the path-based routing. But the class name routing won't work flawlessly in a minification scenario without a very specific module loader that we hack, which I don't want do do. It could work just fine in AOT mode though. So, for the class name approach, we may want to switch it to element name instead, and make it work with gobaly registered custom elements. In JIT-mode the name can be provided by the developer through the decorator or even through a module loader plugin provided by the end user. So, once registered in the container, the router can just resolve them by string name. I believe this is what @jwx is proposing. It's a slight pivot from what is here, but not so fundamentally different. This could be handled easily with AOT either way (which will generate the element decorator and auto-embed the element name based on the class name), but I'm looking for ways to make it work in both JIT and AOT mode without loader hacks. So, I think the element name could unify this.
For the path method looking for something in a "pages" folder, I think I'd want that configurable. It could be done in JIT, but again requires a module loader method, which isn't present anywhere in the framework at this point. It could be done easily in AOT, and in that way isn't much different from the basic name-based mapping.
I may be rambling a bit here. So, apologies. I think this is all very close to what we want, we just may need to tweak it a little based on some underlying constraints of JIT vs. AOT and align with the way the new decorators work. But, again, it's not very far off.
One issue with the approach is as follows.
Assume we have an application with a
<au-viewport>
, and therefore aRouter
, but no routes have been configured.
Assume further we have two available routes by path convention, 'pages/users'
and 'pages/files'
. The we have the following two valid behaviors that are not well defined.
Loads UserComponent
first, fails to load Files
.
UserComponent
.
4 Aurelia finds an <au-viewport>
in the template for UserComponent
.UserComponent
with params { id: '123' }
.{ path: 'user/{id?}', name: 'user', component: UserComponent }
to the RouteTable
.UserComponent
with params `{ id: 'file' }Loads UserComponent
and Files
first.
UserComponent
.<au-viewport>
in the template for UserComponent
.Files
.UserComponent
.Files
with params { id: 'abc-123' }
.{ path: 'files/{id?}', name: 'files', parent: 'user', component: Files }
to the RouteTable
{ path: 'user/{id?}', name: 'user', component: UserComponent }
to the RouteTable
.IRouteConfig
from 9-10.UserComponent
.Files
.I don't see the details of that process here, but there needs to be some way for that data to get into the route table in JIT mode.
The same way that custom elements are registered. I'm not sure the details of that in vNext. In vCurrent I believe it would be .withResources
. I'd peg it to whatever the custom element API is though.
We could make our vNext router depend on
import
as a standard and require a polyfill for that I supposed, and then you could do the path-based routing.
If that is the case, perhaps we put this down as a future plugin enhancement. The plugin depends on the loader / polyfill and configures the router with this behavior. At that point its just a matter of making sure there is a sufficient extension point in the router for this, which I think is very important even if we never made such a plugin.
But the class name routing won't work flawlessly in a minification scenario without a very specific module loader that we hack, which I don't want do.
How are we handling custom element conventions in vNext? Does MyCustomElement still register a custom element '
So, for the class name approach, we may want to switch it to element name instead, and make it work with gobaly registered custom elements.
The difference, and the need for a new convention, is that the convention would plug into the router and register a new route.
For the path method looking for something in a "pages" folder, I think I'd want that configurable.
Agreed. TODO.
I'm all for discarding or relegating path-based convention to a plugin. Just throwing ideas at the wall to see what sticks. As I mentioned in the previous comment, a big problem with that approach is that it is not well defined. Depending on the path you take you may get two different results.
If the class name vs. element name is confusing anyone who reads this, I'll explain a little bit.
Let's say you write the following class:
export class Home {
...
}
Now, if you're using AOT, the compiler is going to come along and do something like this:
import { customElement } from '@aurelia/runtime';
@customElement({
name: 'home', // derived by convention
template: 'your html template from home.html is embedded here by convention'
})
export class Home {
...
}
So, the class name becomes the element name when AOT does it's magic. But, if you are using JIT mode with something like Require.js or System.js, you can use a nice loader plugin to do something like this:
import { customElement } from 'au!./home.html';
@customElement
export class Home {}
Now, here the module loader plugin au
turns the home.html
resource into a template definition and grabs the home
name from the home.html
file name. So, you get a very similar effect as AOT, but in JIT mode and with very little ceremony.
In both AOT and JIT cases, at runtime, you end up with a @customElement
decorator associating the name
value with the class. So, from there, you only need to register it somewhere. In vNext, there's no need for special ViewResources
machinery to register this with. It's all done with the container. In fact, the @customElement
decorator associates an auto-registration behavior with the class, so all you have to do is register the class with DI to turn it into a global resource. That looks something like this:
const container = getContainerFromSomewhereNotShown();
container.register(Home);
So, now you can have a Router
that does something like this:
const path = "home";
const componentInstance = container.get(CustomElementResource.keyFrom(path));
And boom, you've got your string path name mapped to a component. I hope that makes sense.
Note, the au
plugin described above is something I've written and am using in my own app development and testing of vNext. It works exactly as described above :) It literally generates a decorator on the fly based on the framework's decorator with the name, template, dependencies, etc. already pre-configured, so all you have to do is tag it on the class. The plugin actually creates a couple of other things that are handy in other scenarios as well :)
Re the above and team chat, I've removed the path convention from the spec.
@EisenbergEffect Since the router is resolving the string name components against the DI container of the au-viewport
all of your components that are loaded with your au
plugin should (providing I've understood your plugin correctly) be routable right now with both
<a href="home">Home</a>
and
router.goto('home');
without you needing to do anything else. Have you tried it? If not, could you just do a quick test and let me know how it turns out?
The app where I have my plugin isn't currently using the router but it does register all elements from a view into the container as dependencies, so in theory, it should work without any issues.
I am closing this, as the requirements outlined in the original post are all supported by router-lite. The API may look different, but those are supported anyway. Here are the docs for that: https://docs.aurelia.io/router-lite/getting-started.
Feel free t open, if I have overseen a requirement, that is not yet supported.
Route Registration
RouteTable
File Name Convention-based RegistrationIn this proposal, I am going to talk about how to register a route with the router. I'm going to start with the most explicit API and then peel away the defaults and conventions one-by-one, ending with purely conventional routing.
The Route Table
The
Router
will have an instance of theRouteTable
class. TheRouteTable
class will have a reference to the list of all configured routes. TheRouter
should read from theRouteTable
dynamically; if a new route is registered or removed dynamically, it should be immediately available to the Router.Definition
Manual Registration
Many developers may find it more manageable to have all of their route definitions separate from the rest of the code and in a single location. The vCurrent analog for this pattern is
config.map()
inconfigureRouter()
. Having aRouteTable
class that can be injected anywhere will maintain the vCurrent behavior while opening up lots of new opportunities both internally and at an API level.Basic Example
To add a single route, obtain the
RouteTable
and calladd(IRouteConfig)
.Common Use Case
src/routes.ts
AOT mode will automatically generate
src/routes.ts
.Static Property-based Registration
For many developers, having route definitions in the same file as their corresponding component is more straightforward and manageable. Adding a
static routes
property to a class and loading it as a resource will automatically register that component on theRouteTable
.Usage
Adding the
IRouteConfig
to thestatic routes
array will cause the component to be registered against that route when it is loaded as a resource.AOT mode will use this property to generate the
src/routes.ts
.Decorator-based Registration
Decorator syntax is friendly and familiar to many developers, especially those coming from non-JavaScript backgrounds. The syntax is identical to the static property syntax. The decorator itself does nothing more than add the passed
IRouteConfig
to thestatic routes
array.Usage
Class Name Convention-based Registration
By following a simple, understandable class name convention, developers can skip the manual route definition.
Definition
A class with the name
{{Name}}Route
will automatically be registered with the route table with the following configuration.This behavior can be overridden by manually defining a route configuration using either the static property or the decorator method.