Aurelia: AOT Conventions

Created on 1 Dec 2018  路  10Comments  路  Source: aurelia/aurelia

馃挰 RFC

This is to start tracking some notes/scenarios about how we might want to enable conventions via AOT.

馃敠 Context

Most apps will want to ship with as small of a runtime as possible. So, they'll leverage an ahead-of-time compilation step and ship without the JIT compiler. With AOT, in addition to reducing the runtime size, we can also enable a number of interesting conventions to remove more boilerplate code, even more than what we can do with the current version of Aurelia.

We should ship with a number of default conventions out of the box. However, I'd like for conventions to be configurable. The idea here is that Aurelia should let you build your app however it makes sense for you and then you should be able to "teach" it about your architecture, organization, etc. by configuring your own conventions.

Below are a few examples of what you should be able to configure...

馃捇 Examples

  • All files within a folder are custom elements.
  • All public properties on behaviors are bindables.
  • All elements within a folder are routable.
  • All files within a folder are global resources.
  • Use resource name suffixes to determine resource type.
  • Derive the element name from the class export name (or from the file name).
In discussion Needs more feedback Needs more info RFC AOT Conventions Tooling

Most helpful comment

A few more comments on AOT in general :-)

From my point of view, recommendations are good, but rules are more often than not dangerous.
I'm not sure what you have planned for AOT, but I'd be quite worried if AOT were to make assumptions about the folder structure of an app. I'd argue AOT should be an almost entirely transparent optimization, focused exclusively on low-level things, such as template parsing.

At Momondo, we rewrote our entire site using Aurelia - with great success. That success was largely due to the fact that Aurelia is so incredibly flexible, enabling us to hook into just about anything - even a couple of things that were not exactly official extension points, but which were critical to making something of this scale and complexity a work.

Then, shortly after launching the new site, we were bough by Kayak, who clearly didn't give a shit about us or our great work - instead they turned our site into a white-label, and then forced us to start rewriting everything in Angular for absolutely no reason. Yeah, not so fun times.....

Anyways, the reason that story is interesting, is because we got to experience writing the _exact_ same site using both frameworks. And what a difference. Despite being relatively similar on the surface, the fact that Angular relies heavily on AOT, meant that several things that were super easy in Aurelia, became an absolute nightmare to do in Angular. AOT, when applied to the wrong things, essentially force developers to hack the compiler, instead of writing an app - and that's an order of magnitude harder to do.

A couple of examples of major pain points from our experience:

  • Dependency injection
    Aurelia has a very nice dependency injection system, and the fact that we have a hierarchy of containers that we can dynamically register things in, is an extremely powerful concept. Angular uses AOT to generate factories based on types hardcoded in decorators, which is extremely inflexible to work with, and actually make lots of things nearly impossible to achieve.

  • Route configuration
    Aurelia allows us to configure the router at runtime, and therefore also to build a layer on top of it to support our special needs. Angular relies on route configuration hardcoded in decorators, which is just too inflexible for a complex site. For example:

    • How do you localize the URL patterns, to satisfy crazy SEO requirements?

    • How do you generate site maps based on your route configuration, taking into account things like feature flags and locale?

    • How do you support dynamically configuring routes based on feature flags, so when I generate the route named foo route, it can produces either a URL for routing within the app, or an absolute URL that points to the legacy site? Oh, and how do you localize parameters in teh URL?

  • Expression parsing
    Aurelia can be used to parse binding expressions, which allows us to use those for localization. We build a pretty great localization system that relied on the expression parser and value converters to solve some pretty hard problems. Angular uses AOT to parse the expressions at build time, and there's no way to include that parser at runtime. That means, any localized strings that are not hardcoded in the templates, such as document titles, descriptions, and various other things, cannot leverage the expression parser. Would be nice if we could, at a granular level, choose which things to AOT compile, and which parts of the framework should be available at runtime.

AOT made those things a seriously hacky mess in Angular, while Aurelia was easy to extend and build upon. I guess my point is, I really hope you don't take AOT too far, as that has the potential to completely ruin a framework :-)

@EisenbergEffect
Any chance you could write a blog post in the near future, that outlines where Aurelia is headed, especially with regards to AOT and routing? It's pretty hard to get a clear overview of what's going on from just reading the issues in here :-)


Anyways, that became kinda long - on to the thing I actually came back here to suggest.

I just realized how awesome it would be, if we could have a tool that could auto-generate an entire app, with routing and all, based on a text file containing something like this:

    modules
        module1
            components
                component1
        module2

I just sketched a the structure of a new app like that, and there's soooo much boilerplate work to get this thing set up and just create all the files - would be awesome to have a tool that could just do it, based on a couple of recommended structures :-)

All 10 comments

Since you're mentioning project structure here, I thought I'd share the one I've been using for the past few years. We used a variant of it for the rewrite of momondo, and it proved extremely successful at keeping things nice and organized, even as the project grew and multiple teams worked on the code base.

https://gist.github.com/thomas-darling/718f4ca6e7f0ec688b488460e0d0030c

I'm mentioning this here, partly because I think this would be good general guidance on app structure, worth including in the documentation, and partly to show how apps can - and should - be structured in very different ways.

I think this is one area where conventions _could_ be a bad thing, even if they are configurable. I'd be afraid this is something that's just too complicated to generalize and represent in a configuration.
If the framework ends up relying too much on structural conventions, some alternative project structures could unintentionally be made harder, or even impossible, to implement. It could also easily make the framework feel too opinionated - and the fact that it currently isn't, is one of its great strengths.

That said, I agree it's definitely an area worth exploring further :-)

I agree that we need to create docs that talk more about organization patterns. There are two common approaches I've used in real apps. One is very similar to what you've got in the doc linked above and the second is a variation that's more based on feature-folder organization. One thing that could be interesting to do is take these two approaches and create presets for the compiler for each of them. Then, we could make a one-liner AOT config to pick the option. It's all built on a set of lower-level rules, so advanced users can customize. But if we provided the presets out of the box with solid documentation on why you might use this or that preset based on different needs, that could be pretty cool, I think.

Definitely agree with the AOT approach to generate certain presets. For the record, in a very large Aurelia app I once worked on I actually mixed the two approaches - a feature-based separation on the top level and a layer/type-based separation below that, with also a single layer/type-based separation at the top level for things that were truly shared across all features.
I found this particularly helpful as one route contained a full-fledged rules engine builder which had a lot of resources below it, whilst some other routes would typically be mostly crud-ish, and the top-level resources were common things like generic form elements, loaders, context menu. It just kept everything easy to find that way.

Bottom line, there's no one-size-fits-all but I would argue that there are 2 general flavors (which as illustrated above, can also be combined) so I think we should think of some nestable structures that make no assumptions about what they are underneath of.

馃挴 on that @fkleuver. I've also combined those two approaches in pretty much the same way. I like the idea of being able to compose the rules.

A few more comments on AOT in general :-)

From my point of view, recommendations are good, but rules are more often than not dangerous.
I'm not sure what you have planned for AOT, but I'd be quite worried if AOT were to make assumptions about the folder structure of an app. I'd argue AOT should be an almost entirely transparent optimization, focused exclusively on low-level things, such as template parsing.

At Momondo, we rewrote our entire site using Aurelia - with great success. That success was largely due to the fact that Aurelia is so incredibly flexible, enabling us to hook into just about anything - even a couple of things that were not exactly official extension points, but which were critical to making something of this scale and complexity a work.

Then, shortly after launching the new site, we were bough by Kayak, who clearly didn't give a shit about us or our great work - instead they turned our site into a white-label, and then forced us to start rewriting everything in Angular for absolutely no reason. Yeah, not so fun times.....

Anyways, the reason that story is interesting, is because we got to experience writing the _exact_ same site using both frameworks. And what a difference. Despite being relatively similar on the surface, the fact that Angular relies heavily on AOT, meant that several things that were super easy in Aurelia, became an absolute nightmare to do in Angular. AOT, when applied to the wrong things, essentially force developers to hack the compiler, instead of writing an app - and that's an order of magnitude harder to do.

A couple of examples of major pain points from our experience:

  • Dependency injection
    Aurelia has a very nice dependency injection system, and the fact that we have a hierarchy of containers that we can dynamically register things in, is an extremely powerful concept. Angular uses AOT to generate factories based on types hardcoded in decorators, which is extremely inflexible to work with, and actually make lots of things nearly impossible to achieve.

  • Route configuration
    Aurelia allows us to configure the router at runtime, and therefore also to build a layer on top of it to support our special needs. Angular relies on route configuration hardcoded in decorators, which is just too inflexible for a complex site. For example:

    • How do you localize the URL patterns, to satisfy crazy SEO requirements?

    • How do you generate site maps based on your route configuration, taking into account things like feature flags and locale?

    • How do you support dynamically configuring routes based on feature flags, so when I generate the route named foo route, it can produces either a URL for routing within the app, or an absolute URL that points to the legacy site? Oh, and how do you localize parameters in teh URL?

  • Expression parsing
    Aurelia can be used to parse binding expressions, which allows us to use those for localization. We build a pretty great localization system that relied on the expression parser and value converters to solve some pretty hard problems. Angular uses AOT to parse the expressions at build time, and there's no way to include that parser at runtime. That means, any localized strings that are not hardcoded in the templates, such as document titles, descriptions, and various other things, cannot leverage the expression parser. Would be nice if we could, at a granular level, choose which things to AOT compile, and which parts of the framework should be available at runtime.

AOT made those things a seriously hacky mess in Angular, while Aurelia was easy to extend and build upon. I guess my point is, I really hope you don't take AOT too far, as that has the potential to completely ruin a framework :-)

@EisenbergEffect
Any chance you could write a blog post in the near future, that outlines where Aurelia is headed, especially with regards to AOT and routing? It's pretty hard to get a clear overview of what's going on from just reading the issues in here :-)


Anyways, that became kinda long - on to the thing I actually came back here to suggest.

I just realized how awesome it would be, if we could have a tool that could auto-generate an entire app, with routing and all, based on a text file containing something like this:

    modules
        module1
            components
                component1
        module2

I just sketched a the structure of a new app like that, and there's soooo much boilerplate work to get this thing set up and just create all the files - would be awesome to have a tool that could just do it, based on a couple of recommended structures :-)

The way I think of AoT is as a dial that you can leave at zero or turn up to eleven, based on the needs of your app. To leave it at zero basically means that you are going to run your app in JIT mode, with full dynamic everything at runtime. I'm actually building my own app now with vNext using JIT mode, to ensure that it not only works, but that it's a good experience that still feels like Aurelia. You could also dial things up to eleven, which means absolutely everything is static at build time, including DI, routing, pre-compiled templates, component lifecycle optimization, etc. But, I think there's plenty of gradations in-between. Also, various aspects of AoT may, when enabled, use conventions based on today's Aurelia conventions. I also see those conventions being customizable as well.

We haven't built AoT yet, so @thomas-darling we'd love to have you continually engaged as that gets started, enabling us to validate against your app scenarios. This feedback is really awesome and greatly appreciated. We definitely don't want to do anything that shuts out important use cases where Aurelia really shines today.

Also, I'll have a status update on vNext post coming in April. I usually try to do that once per quarter.

It seems you're worried that AOT is an all-in concern @thomas-darling

We are definitely going to invest heavily in AOT, but let me clarify what that means exactly.

AOT is going to detect which things are statically analyzable and can be pre-compiled, unrolled, inlined, optimized and so forth. I have been through a few experimental iterations with AOT-like code locally and came up with an architecture that, while providing sensible defaults, allows you to extend/override/remove just about any aspect of the compilation process. This means more flexibility with AOT, not less.

For example, perhaps something is statically analyzable but you still don't want AOT to touch it because you want to dynamically mutate it at runtime. Now in theory, AOT should be able to detect that anyway and bail out saying "I can't tell for sure what will happen to this", but again those will simply be pluggable "strategies" that can be overridden in a context-sensitive manner.

With Aurelia, we deliberately split "jit" from "runtime". The basic principle of AOT for Aurelia is "AOT calls jit at build time". That does not mean "it's either AOT or JIT". You'll have these flavors:

  • Have AOT completely optimize and inline everything, leaving you with a super fast static website
  • Leave AOT completely out of the picture and rely solely on JIT, keeping your code as-is (this can be particularly useful when plugging Aurelia into a CMS framework or legacy app using progressive enhancement as you won't even need a bundler)
  • Combine both: let AOT optimize whatever it can, and still include the JIT in your app for doing stuff completely dynamically at runtime.

Apart from some conventions and syntactic sugar which can only be done when you actually have the physical files, AOT will not provide runtime features that JIT does not provide. And for the most important conventions we'll still have a requirejs plugin that can work with JIT to provide a little bit of "AOT look and feel".
AOT is all about optimizing app size, startup and runtime performance, and providing a great developer experience via the IDE (where AOT can enhance intellisense and report early errors). As a bonus, not as a mandatory thing :)

@thomas-darling - Check out this .net core console app it does something close to what you are suggesting for scaffolding.

https://github.com/aurelia/dotnet/blob/master/Aurelia.RouteGenerator/Program.cs

I'd like for .?s, .html and .css files with the same name in the same folder (probably with a configurable option) to be tied together into custom elements with automatic imports. I'd also like an easy way to declare custom elements as global resources.

Was this page helpful?
0 / 5 - 0 ratings