26. ● Fully automated documentation generation
● Supports classes OOTB
○ public and private functions
○ public and private fields
○ decorators applied to functions and fields
● Supports ES6 modules OOTB
○ automatically detects exports
● Type inference via plugin and static analysis
31. It’s about sharing conventions with the entire JS community
● Shared tooling and editors
● Shared education, documentation, and general knowledge
● Shared utility code via decorators
57. ● Class decorators are still in flux
● Mixins are very generic - non-Ember libraries exist, no reason to make
something Ember specific
● Spec may be added in the future, leaves room
RFC Usage (Future Ember)
59. ● Class fields on instance, not prototype
○ Need to provide defaults in constructor
● Merged and concatenated fields work as expected
● Decorators for:
○ Computeds
○ Injections
○ Observers and Events
○ Actions
● Extends works both ways (no trapdoor)
● No mixins (except through `.extend`)
RFC Usage (Future Ember)
61. Major caveats
● Merged and concatenated properties do not work
● Observers and events do not work
● `.extend` doesn’t work on ES Classes
Usage with current Ember (2.13+)
69. ● ES Classes make Ember easier to learn
● Allow us to share resources and tooling with wider community
● Bring us more inline with future direction of JS (and Typescript)
● Look better (subjective 😜)
Takeaways
How's it going?
My name is Chris Garrett, and I'm going to be talking today about ES Classes and their future in Ember. But first, the obligatory little bit about me:
I'm a banana slug (UC Santa Cruz grad), bay area native, and have been working with Ember for the last 5 or so years (started just after @machty's router refactor). You may also know as pee-zuraq on Github, Slack, Twitter, and basically everywhere else
I'm a banana slug (UC Santa Cruz grad), bay area native, and have been working with Ember for the last 5 or so years (started just after @machty's router refactor). You may also know as pee-zuraq on Github, Slack, Twitter, and basically everywhere else
I'm a banana slug (UC Santa Cruz grad), bay area native, and have been working with Ember for the last 5 or so years (started just after @machty's router refactor). You may also know as pee-zuraq on Github, Slack, Twitter, and basically everywhere else
I'm a banana slug (UC Santa Cruz grad), bay area native, and have been working with Ember for the last 5 or so years (started just after @machty's router refactor). You may also know as pee-zuraq on Github, Slack, Twitter, and basically everywhere else
I'm a banana slug (UC Santa Cruz grad), bay area native, and have been working with Ember for the last 5 or so years (started just after @machty's router refactor). You may also know as pee-zuraq on Github, Slack, Twitter, and basically everywhere else
and I work here at Addepar on the ICE team. That's Infrastructure, Components, and Ember, because we love our TLAs here.
and I work here at Addepar on the ICE team. That's Infrastructure, Components, and Ember, because we love our TLAs here.
and I work here at Addepar on the ICE team. That's Infrastructure, Components, and Ember, because we love our TLAs here.
and I work here at Addepar on the ICE team. That's Infrastructure, Components, and Ember, because we love our TLAs here.
and I work here at Addepar on the ICE team. That's Infrastructure, Components, and Ember, because we love our TLAs here.
As the frontend infrastructure team at a growing company, we get lots of interesting projects, from upgrades and refactors and tech debt reduction, to libraries like vertical-collection and ember-table that push the capabilities of the browser.
After an initial push to a somewhat modern version of Ember we began working on a project that I think every infrastructure engineer in the relatively short history of web-development has taken on at one point or another:
As the frontend infrastructure team at a growing company, we get lots of interesting projects, from upgrades and refactors and tech debt reduction, to libraries like vertical-collection and ember-table that push the capabilities of the browser.
After an initial push to a somewhat modern version of Ember we began working on a project that I think every infrastructure engineer in the relatively short history of web-development has taken on at one point or another:
Build a component library (like bootstrap!)
Yes, at some point every team realizes they have been building the same components in various ways and it's time to consolidate into a shared set of patterns. Now I'm not here to sell you on component libraries, maybe you have one, maybe you don't. But probably one of the most important things about a solid and usable component library, and certainly the thing that made Bootstrap and others so popular and successful, is clear, open, and accessible documentation.
Early on we realized that as a relatively small team within a relatively small company, we likely wouldn't have the resources to maintain a full documentation website like major frameworks. So I started looking into documentation generators:
Build a component library (like bootstrap!)
Yes, at some point every team realizes they have been building the same components in various ways and it's time to consolidate into a shared set of patterns. Now I'm not here to sell you on component libraries, maybe you have one, maybe you don't. But probably one of the most important things about a solid and usable component library, and certainly the thing that made Bootstrap and others so popular and successful, is clear, open, and accessible documentation.
Early on we realized that as a relatively small team within a relatively small company, we likely wouldn't have the resources to maintain a full documentation website like major frameworks. So I started looking into documentation generators:
First up was YUIDoc. It's a classic, and officially the unofficial standard within the Ember community due to its use for the Ember and Ember-CLI API docs. However, while it may have been revolutionary for its time, by today's standards it is sorely lacking.
As you can see it requires tags for every single thing, lacking even the most basic doc generation functions. In terms of maintenance and upkeep, this was only one step below building a full site.
Additionally it has no support for ES Modules or non-class based utility functions.
First up was YUIDoc. It's a classic, and officially the unofficial standard within the Ember community due to its use for the Ember and Ember-CLI API docs. However, while it may have been revolutionary for its time, by today's standards it is sorely lacking.
As you can see it requires tags for every single thing, lacking even the most basic doc generation functions. In terms of maintenance and upkeep, this was only one step below building a full site.
Additionally it has no support for ES Modules or non-class based utility functions.
First up was YUIDoc. It's a classic, and officially the unofficial standard within the Ember community due to its use for the Ember and Ember-CLI API docs. However, while it may have been revolutionary for its time, by today's standards it is sorely lacking.
As you can see it requires tags for every single thing, lacking even the most basic doc generation functions. In terms of maintenance and upkeep, this was only one step below building a full site.
Additionally it has no support for ES Modules or non-class based utility functions.
Then there was JSDoc, another long-lived doc library.
While it has been getting better over time, you can see that it's basically the same level tagging required as for YUIDoc. In fact, the only difference here is that rather than stating the name of this method, `isFoo`, we declare that it belongs to the class.
It does have some support for modern ES features, but development has been very slow so ultimately it was not ideal.
Then there was JSDoc, another long-lived doc library.
While it has been getting better over time, you can see that it's basically the same level tagging required as for YUIDoc. In fact, the only difference here is that rather than stating the name of this method, `isFoo`, we declare that it belongs to the class.
It does have some support for modern ES features, but development has been very slow so ultimately it was not ideal.
Then there was JSDoc, another long-lived doc library.
While it has been getting better over time, you can see that it's basically the same level tagging required as for YUIDoc. In fact, the only difference here is that rather than stating the name of this method, `isFoo`, we declare that it belongs to the class.
It does have some support for modern ES features, but development has been very slow so ultimately it was not ideal.
After a bit more googling I stumbled upon ESDoc. Unlike the other two libraries, this one was newer and built with ES6 in mind. In fact, it _requires_ the user to use new features such as ES classes and modules. I played around with it a bit and the results were amazing.
As you can see, this is the same example as before rewritten with ES Classes, and with just a couple more fields to showcase some of ESDocs' features. The doc comments are very minimal, there are just a few tags required for parameters and that's it.
From this we get a full page devoted to our class, along with a nice class link in the sidepanel there.
It lists out the private and public fields and functions, and even lists decorators that have been applied to each one.
There's even a plugin in the works to add basic inferable typing information, such as the default types of fields (based on default values).
From this we get a full page devoted to our class, along with a nice class link in the sidepanel there.
It lists out the private and public fields and functions, and even lists decorators that have been applied to each one.
There's even a plugin in the works to add basic inferable typing information, such as the default types of fields (based on default values).
From this we get a full page devoted to our class, along with a nice class link in the sidepanel there.
It lists out the private and public fields and functions, and even lists decorators that have been applied to each one.
There's even a plugin in the works to add basic inferable typing information, such as the default types of fields (based on default values).
This is a massive tooling win. Maintaining YUIDoc and other forms of automated documentation that require lots of tagging, updating, and manual tinkering is not easy, and results in many hours of developer labor being eaten up slowly over time. From an infrastructure perspective, this is huge.
And this is where I began to see the benefits of ES Classes.
If you're like me, you may not have paid much attention to the emerging ES class model.
After all, we had a rock solid object model in Ember, and it's used literally everywhere, changing now would be a major refactor.
All of that work for what boils down to a slightly cleaner syntax - is it really worth it?
If you're like me, you may not have paid much attention to the emerging ES class model.
After all, we had a rock solid object model in Ember, and it's used literally everywhere, changing now would be a major refactor.
All of that work for what boils down to a slightly cleaner syntax - is it really worth it?
If you're like me, you may not have paid much attention to the emerging ES class model.
After all, we had a rock solid object model in Ember, and it's used literally everywhere, changing now would be a major refactor.
All of that work for what boils down to a slightly cleaner syntax - is it really worth it?
Now that classes are finally getting some of the features they needed, and wider spread adoption, more and more tools like ESDoc are going to start emerging.
The Ember Way has always been about convention over configuration, and the next step here is becoming clearer every day: By switching from the Ember Object model to ES Classes, we'll get the benefit of sharing conventions with the entire javascript community.
Shared tooling and editors, shared education and community knowledge (no more having to teach the object model to new hires), and even shared code via decorators (we may even be able to contribute back to this new ecosystem - consider genericizing computeds and make them available anywhere, not just in Ember).
When I realized this I started to look more deeply into classes and what would be necessary to do to get them going in Ember. The result of that research was the ES Class RFC, which was accepted just recently.
Of course, you may have heard the old adage - it takes a village to write an RFC, and that's especially true in the Ember community. With that in mind, I want to thank @rwjblue, who helped immensely with the RFC process, and @runspired, who showed me that it would be possible to use classes in the first place.
When I realized this I started to look more deeply into classes and what would be necessary to do to get them going in Ember. The result of that research was the ES Class RFC, which was accepted just recently.
Of course, you may have heard the old adage - it takes a village to write an RFC, and that's especially true in the Ember community. With that in mind, I want to thank @rwjblue, who helped immensely with the RFC process, and @runspired, who showed me that it would be possible to use classes in the first place.
Now without further ado, let's dive in!
The RFC was just merged recently so the work to get it working as spec'd is still underway. I'm going to start by going over the API as spec'd, and then go into how you can use classes today, and how you can use them with older versions of Ember, all the way back to 1.11.
One last quick warning: Class fields are stage 3 and Decorators are stage two, meaning they are still experimental at this point and subject to change. Both of these are fairly necessary to make ES Classes work well with Ember, so building with them is definitely a little risky. You have been warned.
First up, usage as specified by the RFC
So the first thing you'll want to do when getting started is to install `ember-decorators`.
The ember-decorators library is not officially part of Ember, but it provides a lot of basic utility decorators and also adds the babel transforms for both decorators and class fields to the build process for your app.
Here you can see a minimal custom input component written in the standard Ember syntax.
And here is the same thing rewritten using ES classes.
As you can see it's pretty similar to the Ember object model, just a little cleaner, equals instead of colons, semicolons instead of commas, and a class name. Properties can be set right on the class, as expected, and merged and concatenated properties like `attributeBindings` should work as intended.
One thing to note here is that while you _can_ technically omit the class name and export an anonymous class here, you shouldn't as one of the benefits of ES Classes is they provide named prototypes, as you can see here. This will be especially useful if you have classes that don't get resolved by the Ember resolver, since that is how Ember currently applies class names to things.
Here you can see a minimal custom input component written in the standard Ember syntax.
And here is the same thing rewritten using ES classes.
As you can see it's pretty similar to the Ember object model, just a little cleaner, equals instead of colons, semicolons instead of commas, and a class name. Properties can be set right on the class, as expected, and merged and concatenated properties like `attributeBindings` should work as intended.
One thing to note here is that while you _can_ technically omit the class name and export an anonymous class here, you shouldn't as one of the benefits of ES Classes is they provide named prototypes, as you can see here. This will be especially useful if you have classes that don't get resolved by the Ember resolver, since that is how Ember currently applies class names to things.
Here you can see a minimal custom input component written in the standard Ember syntax.
And here is the same thing rewritten using ES classes.
As you can see it's pretty similar to the Ember object model, just a little cleaner, equals instead of colons, semicolons instead of commas, and a class name. Properties can be set right on the class, as expected, and merged and concatenated properties like `attributeBindings` should work as intended.
One thing to note here is that while you _can_ technically omit the class name and export an anonymous class here, you shouldn't as one of the benefits of ES Classes is they provide named prototypes, as you can see here. This will be especially useful if you have classes that don't get resolved by the Ember resolver, since that is how Ember currently applies class names to things.
Now, you may want to pass in a value to the input from the surrounding context,
and if you were to do that right now you would find that it wouldn't work at all.
This is because class fields are actually _instance_ state rather than _prototype_ state - they essentially boil down to this example here. The value passed in would be set in the constructor's call to `super` meaning that the default value we provide would overwrite it.
Now the RFC specifies a change here, and there's a PR out to implement it, that will pass the initialization properties into the constructor like so, giving you a chance to override them before they are finalized.
Another technique you can use is something like this. The expression gets run when the field is set, so it’ll be able to check if the value is defined and set it if not. We may add a utility decorator which does this as well.
Now, you may want to pass in a value to the input from the surrounding context,
and if you were to do that right now you would find that it wouldn't work at all.
This is because class fields are actually _instance_ state rather than _prototype_ state - they essentially boil down to this example here. The value passed in would be set in the constructor's call to `super` meaning that the default value we provide would overwrite it.
Now the RFC specifies a change here, and there's a PR out to implement it, that will pass the initialization properties into the constructor like so, giving you a chance to override them before they are finalized.
Another technique you can use is something like this. The expression gets run when the field is set, so it’ll be able to check if the value is defined and set it if not. We may add a utility decorator which does this as well.
Now, you may want to pass in a value to the input from the surrounding context,
and if you were to do that right now you would find that it wouldn't work at all.
This is because class fields are actually _instance_ state rather than _prototype_ state - they essentially boil down to this example here. The value passed in would be set in the constructor's call to `super` meaning that the default value we provide would overwrite it.
Now the RFC specifies a change here, and there's a PR out to implement it, that will pass the initialization properties into the constructor like so, giving you a chance to override them before they are finalized.
Another technique you can use is something like this. The expression gets run when the field is set, so it’ll be able to check if the value is defined and set it if not. We may add a utility decorator which does this as well.
Now, you may want to pass in a value to the input from the surrounding context,
and if you were to do that right now you would find that it wouldn't work at all.
This is because class fields are actually _instance_ state rather than _prototype_ state - they essentially boil down to this example here. The value passed in would be set in the constructor's call to `super` meaning that the default value we provide would overwrite it.
Now the RFC specifies a change here, and there's a PR out to implement it, that will pass the initialization properties into the constructor like so, giving you a chance to override them before they are finalized.
Another technique you can use is something like this. The expression gets run when the field is set, so it’ll be able to check if the value is defined and set it if not. We may add a utility decorator which does this as well.
Now, you may want to pass in a value to the input from the surrounding context,
and if you were to do that right now you would find that it wouldn't work at all.
This is because class fields are actually _instance_ state rather than _prototype_ state - they essentially boil down to this example here. The value passed in would be set in the constructor's call to `super` meaning that the default value we provide would overwrite it.
Now the RFC specifies a change here, and there's a PR out to implement it, that will pass the initialization properties into the constructor like so, giving you a chance to override them before they are finalized.
Another technique you can use is something like this. The expression gets run when the field is set, so it’ll be able to check if the value is defined and set it if not. We may add a utility decorator which does this as well.
Now let's dive into decorators themselves. Again, these aren't official Ember APIs yet so while they are stable, they may change as classes become more standardized and official Ember decorators replace them.
Here we have computed properties in both the old style on the left and the ES class style on the right. As you can see, the ES class style on the right uses getter and setter functions, bringing us more in line with standard JS. We can also apply the `@readOnly` decorator directly to the computed, rather than calling it as a function on the property like in the previous style.
Quick note, it is currently possible to use the `@computed` decorator with functions, but that will probably be removed in future versions of Ember decorators in favor of this style.
We can also use almost all of the computed property macros as decorators. Simply apply them to empty class fields. The only macro which was not translated was the `readOnly` macro to avoid naming collisions, you can use `@readOnly @alias` to get the same effect.
Here you can see how we do service injection. As you can see, we can apply the decorator to fields just like the helper was used in the older style, optionally providing a name for the service.
And here we can see using decorators for events and observers. It's really much cleaner this way, we can just apply them directly to the function. Note that the decorator is called `@observes`, not `@observer`, a decision which was made early on.
Finally we have actions, which can now be specified directly on functions using the `@action` decorator.
A quick note about that, unlike the previous style where actions were solely functions on the `actions` hash, these functions remain on the class itself. Under the hood, a function is added to the actions hash which simply calls the decorated function. This can lead to property name collisions, so be aware
Finally we have actions, which can now be specified directly on functions using the `@action` decorator.
A quick note about that, unlike the previous style where actions were solely functions on the `actions` hash, these functions remain on the class itself. Under the hood, a function is added to the actions hash which simply calls the decorated function. This can lead to property name collisions, so be aware
For more details on the decorators, you can check out the API docs which are linked on the Github repo (and generated with ESDoc).
Another feature touched on by the RFC was cross compatibility when extending ES classes - that is, being able to extend existing classes using ES class syntax, and being able to use dot-extend on ES classes to go back to the old Ember style. This will work in both cases once the RFC is fully implemented, meaning you'll be able to write ES classes in a piecemeal way to convert portions of your codebase at a time.
Finally, Mixins were not covered by the RFC, and currently there isn't a way to use them with the new syntax. This is for a few reasons:
1. Class decorators are still in a lot of flux, and likely will change a bit more
2. Mixins are a very generic pattern. Non-ember specific libraries exist, and more will probably emerge over time, so creating an Ember specific mixin library does not provide any immediate benefit
3. The spec itself may decide to add mixins or traits at some point in the future, which would definitely supercede whatever implementation we come up with
Much of the functionality provided by mixins can be refactored these days into services or utility functions. If you just can't live without them though, you can still use dot-extend to add mixins to a superclass and then extend that class.
Finally, Mixins were not covered by the RFC, and currently there isn't a way to use them with the new syntax. This is for a few reasons:
1. Class decorators are still in a lot of flux, and likely will change a bit more
2. Mixins are a very generic pattern. Non-ember specific libraries exist, and more will probably emerge over time, so creating an Ember specific mixin library does not provide any immediate benefit
3. The spec itself may decide to add mixins or traits at some point in the future, which would definitely supercede whatever implementation we come up with
Much of the functionality provided by mixins can be refactored these days into services or utility functions. If you just can't live without them though, you can still use dot-extend to add mixins to a superclass and then extend that class.
Finally, Mixins were not covered by the RFC, and currently there isn't a way to use them with the new syntax. This is for a few reasons:
1. Class decorators are still in a lot of flux, and likely will change a bit more
2. Mixins are a very generic pattern. Non-ember specific libraries exist, and more will probably emerge over time, so creating an Ember specific mixin library does not provide any immediate benefit
3. The spec itself may decide to add mixins or traits at some point in the future, which would definitely supercede whatever implementation we come up with
Much of the functionality provided by mixins can be refactored these days into services or utility functions. If you just can't live without them though, you can still use dot-extend to add mixins to a superclass and then extend that class.
To summarize,
* Class fields are now instance state, not prototype state, and default values need to be set in the constructor
* Merged and concatenated fields should work as expected
* Decorators exist for computeds, injections, observers and events, and actions
* Extends works both ways, the two styles are fully interoperable
* Mixins are not supported in ES class syntax (yet)
Alright, so that's how you'll use ES classes once the RFC has been implemented. Now let’s talk about how you can use them right now with modern Ember
It is possible to use them, but there are a few caveats:
* Merged and concatenated properties don't work at all, they just overwrite the superclass's values
* Observers and events also do not work, they refuse to fire
* dot-extend does not work. This means that once you start using ES Classes, you can't go back - it's a trapdoor.
While there won't be a fix for dot-extend, we do have plans for compatibility helpers which you will be able to use. They _are_ a work in progress right now, so while they should be available soon you can't use them now.
For merged and concatenated properties such as `classNames`, `classNameBindings`, and `attributeBindings` we are planning on adding simple general purpose decorators, `@merged` and `@concatenated`. These will just merge the property with the superclass's and as a bonus are declarative so you know what's going on.
We are also considering adding specialized decorators like `@className` which will allow you to annotate functions and properties directly since those are the majority use case for merged and concatenated properties.
And for events and observers we are considering creating some legacy class decorators which will add the preprocessing necessary to make events and observers work on existing objects. This makes events and observers opt-in rather than on-by-default on all objects.
Alright, so that's modern Ember. What about legacy versions? Like I pointed out earlier, we're on Ember 1.11 here at Addepar (soon-to-be 1.13), but we are already using ES Classes in some of our library code.
It has the same caveats as modern Ember, with one more major one - the constructor does not work at all. As I pointed out earlier, class fields actually get assigned in the constructor, so they don't work either.
It has the same caveats as modern Ember, with one more major one - the constructor does not work at all. As I pointed out earlier, class fields actually get assigned in the constructor, so they don't work either.
The solution here will be a special `@proto` decorator which we're working on. All it does is assign the value of the field directly to the prototype of the class, rather than to the instance. For initialization work which you would do in the constructor, you can use the `init` hook.
Alright, so to summarize:
ES Classes make it easier to learn and use Ember, especially for non-Emberists and newcomers.
They allow us to share resources and tooling with the entire Javascript community.
They bring us closer to the future of Javascript as a whole, and pave the way enabling Typescript as an option with Ember.
And they just look better, right? :P