My week with vNext

I’ve spent the past four days with vNext and like to share my experiences.

What did I do?

I’ve tried to port one of our internal apps from Aurelia 1 to Aurelia 2.

It’s a rather small app with only six views (3 list views, 2 detail views and 1 special case), but it uses the same codebase as our largest Aurelia app. The list views contain lots of complex components, filters, and actions and the detail views use highly integrated stuff like custom validation renderers.

It’s an intense test and beats the hell out of realworld and other toys.

Summary (tl;dr)

  • It was a rough ride (as expected, in pre-alpha), but I really enjoyed it. I originally planned to try Aurelia 2 for only two days –but here I am, in the midst of day 5 and still fiddling with it. It gripped me :blush:
  • I didn’t get as far as I hoped, but most of what I saw was quite encouraging.
  • I strongly encourage more people to give it a try. (Well, maybe wait until the router is in better shape.)

How did it go?

The good

  1. The core team! Huge thanks to @jwx, @fkleuver, @bigopon, @EisenbergEffect and all the others for being very helpful via Discord and the issue tracker. I felt very welcome!

  2. The build process is much simpler than in Aurelia 1. I’m sure this will help many people who want to give Aurelia a try. For example, for webpack it’s a very straight forward configuration. I really don’t miss aurelia-bootstrapper, the buggy aurelia-polyfills, and all the other cruft. :ok_hand:

    aurelia-loader is used to implement many of the conventions that made Aurelia 1 so nice and easy to work with. (With some caveats – see below).

  3. Writing tests is incredibly easy. I’ve found several issues in Aurelia and it was straightforward to write test cases that reproduced the problems (example 1, example 2). (I didn’t write any test for our own app in those four days.)

  4. It feels very familiar. Many things, including complex stuff like I18N, work pretty much exactly as before.

    Hadn’t it been for the router and several bugs I encountered, I’m quite sure I could have ported the whole app in a day or two – including converting it to TypeScript with almost no prior experience in TS (see below).

  5. Several tiny improvements made me very happy. I really like that I don’t have to manually specify @inject anymore. And it’s nice that the topmost <template> wrapper is no longer necessary in every HTML file.

The not-so-good

Please take everything below with a large bucket of salt. I’m fully aware that I did the equivalent of walking onto a large construction site and trying to move in and live there. I know I can’t expect the elevators to work and the walls to be painted.

That being said, here’s what I found:

  1. The router, again! The old router was barely useable for us. But with custom forks and fixes for router and history, we could make it work.

    The new one is (currently!) much worse. It doesn’t fulfill any of our core use-cases. Its current interface and mode of operation are often the complete opposite of how I would have approached it. I gave up on it after the first day and had to fall back to just porting our most complex view instead of getting the whole app up and running.

    That being said, the team assured me that the router is “just not there yet” and will work as I expected. I’m not fully convinced, but I’ll give the team the benefit of the doubt and wait for what’s coming.

  2. I miss the old <slot>s. The new Shadow DOM implementation has drawbacks for our use-case.

  3. I felt the Second-System Effect in several places. While v2 is much simpler than v1 in some aspects (see above), it feels more complicated and over-engineered in others. It’s not really something concrete I can point at, but the “over-abstraction spider sense” I’ve developed over the past 20 years started tingling.

    This is highly subjective and I’d have to spend more time with the codebase to better articulate what I mean, but here are some rough examples:

    • Lots of new interfaces where you have to know both the Interface and the implementation (eg., I18N) or have to know at what level of the abstraction you’re supposed to enter. (eg., shall I inject IDOM or HTMLDOM?)

      This seems to be caused in part by Aurelia trying to fit many more use-cases, which automatically makes it more difficult for users to find the correct angle for their particular use-case.

    • Interfaces whose benefit I don’t understand. For example IRouter. My take is: the odds that I would replace the router and the replacement would still adhere to IRouter are close to zero. So why have that extra layer of abstraction in the first place?

    • aurelia-pal was quite straight-forward to use. But IDOM/HTMLDOM can’t be used until Aurelia has been started (which complicated some of our use-cases.)

  4. The conventions and aurelia-loader interact with some features in unexpected ways, causing things (currently!) to sometimes stop working unexpectedly. The alternative – having to manually annotate everything – would eliminate much of the elegance that drew me to Aurelia in the first place.

    Some examples:

    • <import from> (the new <require from>) can’t be used to import routing components – at least I couldn’t get it to work. I had to use @customElement({dependencies:…}) on the class instead.

    • <import from> (for normal components) stops working if the corresponding class has a @customElement or @view annotation. (Which is amplified by the previous point.)

    • The auto-injection stops working as soon as you annotate one of the constructor arguments – which you’re sometimes forced to do, due to the way interfaces / implementations are currently structured.

      For example, @aurelia/i18n requires you to inject as @I18N i18n: I18nService instead of just i18n: I18nService or i18n: I18N (for reason’s I don’t understand). But then you have to annotate all your constructor arguments for that class or DI will throw up.

Other stuff

This was also the first time I’ve given TypeScript a serious try. When I last attempted to adopt TS – 4 years ago – it was a horrible experience and I bailed within hours.

This time it was much better. At first I tried to apply a strict TS configuration. But this created way too much friction and turned my “migrating to Aurelia 2” task into a huge “migrating to TS” task – which was not my goal for this week. After adopting a more lenient approach, it started being useful without being very obstructive.

I’m still not fully convinced TS provides many benefits for frontend apps. Many of our regressions and bugs arise between templates and code – where TypeScript can’t help you. But it also doesn’t hurt, so I’ll continue to use it for now and see how it goes. And I’ll definitely consider adopting it for our NodeJS backend, where it could be much more helpful – if compile times don’t get out of hand.

18 Likes

Thank you for giving things a try @rluba and your feedback has been very helpful to us!

I’ll reply directly to some of your points where I feel it might add important context for other readers.

The good

Glad to hear! The improved community interaction was certainly part of the plan. We do encourage people to read/open issues and/or join Discord if they haven’t already.

Getting rid of requirejs as a runtime dependency and replacing it with a DI-based resource system that works with imports is the key here. It allows every other bundler to understand Aurelia apps without extra sorcery and that makes a huge difference :slight_smile:

Glad you think so too. It’s also been a pleasure for us as the core team and one of the reasons why the test coverage is so much better on some complex runtime concerns. We hope more people come to appreciate this as a new core strength of Aurelia.

The not-so-good

Noted.

What’s reassuring about your criticism is that none of it came as a surprise. Most of the walls you ran into are straight up things that have been in the planning for a while.

What’s valuable about your criticism is that it made us reassess our priorities. It’s clear that we need more feedback from people with the angle of migrating an existing v1 app, and for this to work we probably shouldn’t leave the “v1 compatibility” dangling at the bottom of our priority list as something “we can always slap on right before going alpha”.

That being said, to avoid confusion for other readers let me reiterate a few things already pointed out on Discord in response to some of those points.

Let me help you understand why it is the way it is, and hopefully convince you. There’s no reason why we can’t be open about the particulars of the planning here.

Last year, the plan was that I would personally migrate the router from v1 with the help of @davismj 's specs. Soon I postponed that plan in favor of working on Binding instead. And then the JIT. And then tests. And, and, and. Let’s just say I got busy. Really really busy :slight_smile:

Then @jwx came along and said he would like to work on the router for v2.

He had some creative ideas about an alternative API that would rely on conventions and custom URL syntax rather than pre-configured routes. Several months later - while it’s incompatible with v1 from a migration point of view, it has many more capabilities at this point than v1 did and is much more robust in providing those capabilities. The only caveat is that custom URL syntax which a lot of people really dislike.

We had countless heated internal discussions about this in the past months - I’m actually impressed with @jwx’s willpower and resilience to keep going and try to realize his vision regardless of some of the harsh criticism coming from myself and others in the core team.

Ultimately though we let things run its course because the idea was that when I’m done with the other things, I would circle back to the router and fix whatever still needed fixing at that point.

Now we’ve reached a point where most of the hard work has been done. Migrating the configuration APIs from v1 would only be 20% of the work compared to if it had to be done from scratch - @jwx made this thing much more flexible and extensible than it may look like from the outside. The surface API’s just aren’t fully fleshed out yet.

Unfortunately for those looking to migrate from v1, it’s kind of “the wrong way around”! It’s almost as if the router isn’t there yet at all. We know. It will get there. We said we would make migration from v1 largely painless, we’re not backing down from that. There’s a reason we’re not in alpha yet.

The functionality hasn’t gone anywhere, it’s just getting a different name that doesn’t clash with native ShadowDOM.

No, you don’t. This is solely a symptom of pre-alpha ness. We’re currently exporting a bunch of stuff that are not supposed to be part of the final public API.

It’s largely due to us trying to create a simpler API with more power. v1 has too much (hard to discover) API surface. In v2 we’re trying to bring all those hidden nooks and crannies to the front via well-defined contracts, of course adding capabilities and features in line with what has been requested

The confusion is understandable and we’ll need to think about this feedback as an aspect we need to make more obvious to end-users.

In any case, to clarify: some of the interfaces are not meant to be abstractions! Some of them are, but in general it allows us to do a nice little trick that classes won’t allow us to do: export the interface together with a same-name parameter decorator that you can use to inline-annotate your constructor parameters for DI.

The other thing is that it prevents users from directly instantiating them (DI is what you should be instantiating them with, to keep constructor signatures as internal API), or monkey-patching prototypes (something even some official v1 plugins did!). We may or may not keep it this way, but we do want to try to tighten the API surface to contracts/interfaces as it forces us to improve the public API when needed, instead of monkey patching stuff, which is a recipe for long term maintainability disaster.

Yep. Will be addressed.

Ditto :slight_smile:

All in all, we deeply appreciate your willingness to try v2 in its super early state and your feedback has been very helpful, in particular those bugs you found (and wrote tests for)!

And I hope my response does not come across as a “counter” to criticism. The only reason I addressed those points is because it might give some people (who haven’t read our discussions on Discord) the wrong idea about where v2 is going in some areas.

20 Likes

I’d also like to give a huge thanks to @rluba for being willing to jump in with something we haven’t even labeled as alpha yet. I’m very encouraged by the post above, especially since even our pre-alpha bits managed to suck you in :slight_smile:

@fkleuver Has already addressed most items in detail, so I won’t get into that in my response. Suffice it to say, a lot of the less-than-desirable experiences you had were precisely why we haven’t felt comfortable launching an alpha, even though I (and others) have been building apps with this for a good bit of time. We really don’t want our community to have anything but the best of experiences with the alpha, and it’s clear we’re not there yet.

Some broad things I took away from this post:

  • Aurelia 2 is heading in the right direction and when it fully arrives I think we will delight our existing community and grow the community as well.
  • Since many of the negative items listed above were things we already knew about, you’ve helped validate our understanding of the overall status, including our decision to not rush to alpha.
  • For several items, you’ve helped us take a second look at things and realize where we may have “brushed some things under the rug”. So, we know a few new areas that we still need to refine and improve.

All in all, this has been great and we’d like to encourage anyone who has some extra time to please dig into the RFCs, try out the pre-alpha bits, and provide us with some feedback.

As promised in another discourse post, we’ll have a broad status update soon. I’m planning one for this month, probably towards the end of the month, where we’ll detail how far we’ve come, and what we’ve got left to get to the alpha stage.

Lots of good stuff is on the way. We thank everyone for your patience and support!

14 Likes

@aurelia/i18n requires you to inject as @I18N i18n: I18nService instead of just i18n: I18nService or i18n: I18N (for reason’s I don’t understand).

The dependency injection works by using metadata thats added to the type at compile time. Interfaces have never worked with Aurelia DI because they are not emitted in the JavaScript output so there is no type to link to.

@fkleuver, I’m sure this has been discounted because it’s a nasty hack… but we got around this by declaring all our interfaces as abstract class rather than interface. This way they work in DI without annotating and you can still use implements and use them like an interface.

1 Like

This idea has crossed my mind as well.

The thing is, if you use a class as an interface type annotation it only works if you build with TypeScript, in other words, it depends on build tools.
Then we might as well just solve the problem with AOT, and let our AOT compiler wire things up based on the type annotations (in which case even plain interfaces would work because AOT would understand what classes they map to).

So, you could do something like this:

export interface IBar {}
export class Bar implements IBar {}

export class Foo {
  constructor(private bar: IBar) {}
}

In the meantime, there will also be the parameter decorator variant which will work in vanillajs (aka script tag, etc) without any tooling, if/when the decorator proposal lands:

export const IBar = DI.createInterface().withDefault(x => x.singleton(Bar));
export class Bar {}

export class Foo {
  constructor(@IBar bar) {
    this.bar = bar;
  }
}

It’s just that right now we have this sort of ugly/verbose looking semantic because… we aren’t done yet :wink:

export class Foo {
  constructor(@IBar private bar: IBar) {}
}
2 Likes