Upgrade to Pro — share decks privately, control downloads, hide ads and more …

EmberConf 2016 – Idiomatic Ember (Speaker Notes)

EmberConf 2016 – Idiomatic Ember (Speaker Notes)

This talk was presented at EmberConf 2016, and the full slides can be viewed here: https://speakerdeck.com/poteto/emberconf-2016-idiomatic-ember-finding-the-sweet-spot-of-performance-and-productivity

Lauren Tan

March 30, 2016
Tweet

More Decks by Lauren Tan

Other Decks in Programming

Transcript

  1. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 IDIOMATIC EMBER Finding the Sweet Spot of Performance & Productivity h D O C K YA R D h • hi everyone! • today i want to share some ideas with you about how we can write idiomatic ember apps that are both performant and productive IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 bit.ly/idiomatic-ember • if you can't see the slides clearly, you can look them up on • bitly idiomatic dash ember (repeat it)
  2. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • but first with a heavy heart, let's address the elephant in the room – • what's an idiomatic talk without idioms? IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • they say time is money • so i'm gonna just cut to the chase
  3. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 LAUREN TAN SUGARPIRATE_ POTETO • my name is lauren, and i was born in the year of the rabbit • you can find me on the interwebs as sugarpirate or poteto on github IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • when i'm not working on client work or open source, i sometimes play final fantasy 14 • it's a game that can be quite addictive, but thankfully i have
  4. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 ZELDA • a little puppy Zelda who keeps me grounded in reality • unfortunately she couldn't be here today to give this talk, so you'll have to listen to me instead IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 I WORK AT DOCKYARD • a year ago i was still living in australia • but i moved to Boston last year to work at DockYard • we work on a ton of Ember and Elixir apps as well very many open source libraries including ember addons • in 2 days it will be the end of my 1st year at dockyard, and i have to say that it's been one of the best jobs i've ever had
  5. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • the best part is, we're hiring senior ember engineers • so please talk to me, brian, marten or estelle later if you're interested in finding out more IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • and if you come speak to me after my talk, i'll give you a very limited edition sugarpirate sticker!
  6. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 THE STATE OF JAVASCRIPT IN 2016 • now before we start talking about ember, let's look at what it's like to build a modern javascript app in the year 2016 IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • react has been steadily gaining ground and mindshare • and for good reasons • some of its ideas have even made their way into ember and other frameworks
  7. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • but despite how far we've come from our early days, modern web dev is getting really hard • i mean, it used to be so simple, right? IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • in the Good Old Days™, all you had to do was write some html, maybe some css, slap some jQuery on a page • and even use a plugin or 5
  8. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • but these days we spend so much time configuring things • and we spend valuable time writing glue code instead of working on what matters IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • you have to make a ton of choices for your front end stack • which can be quite paralyzing
  9. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 leftpad • & its easy to build a tangled web of dependencies • which can lead to all sorts of problems… • <dramatic pause> IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 “I hand-rolled my own front-end stack using the most organic, gluten-free and artisanal micro- libraries.” • it's tempting to fall victim to javascript hype • and try to build your own makeshift framework • that might end up slowing you down bc you have to make sure everything plays nicely together • why go through that pain?
  10. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • but that's why we use an opinionated framework like ember, right? • with ember, we opt into convention over configuration • problems are shared across the community • bc chances are, your app isn't a special snowflake IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 Performance Productivity • these strong standards have given the ember community hands down the best tooling experience available • we have things like ember-cli, the ember inspector, more than 2,000 addons, which is an incredible number • and all of this would not have been possible without the community
  11. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 ❤ ❤ • and i think it's fair to say that ember has one of the most welcoming and friendly communities out there • our slack channel is always active, and getting help is easy • there are a few people in there that don't seem to sleep • so if you have a question, there will almost always be someone available to help you out IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 THE EMBER WAY? • because ember is opinionated, it also means there is an "ember" way of doing things • instead of rebuilding the wheel and inventing your own abstractions • we leverage the community's exp in building web apps • with ember 2.0 being released last year in august, quite a few things have changed since then • changes are main motivation for talk
  12. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 Data Component Owner Action • for example, there has been lots of discussion around data down, actions up • this is a pattern for one way data flow that we adapted • and is meant to make your apps easier to reason about & maintain • i'll cover this more in detail later IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • we've also established conventions that singleton state should live in services instead of controllers • services are excellent for handling the long lived state of things such as shopping carts, activity feeds and notification messages
  13. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 this.attrs.fooAction(val); this.get('fooAction')(val); VS • actions have been getting a little confusing lately • with the future introduction of glimmer components • these action calls will be subtly different, although they may appear to be the same right now IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • we also know that controllers are going away in the future • i'll talk a little more about what that means and share some techniques you can use to ease the transition to routable components
  14. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • in a nutshell, things have changed • and dealing with change can be hard • these changes can be diff to deal with bc • things that used to be best practices may no longer be true • and lack of updated info about new way of doing things IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 MORE JAVASCRIPT • going forward, we can expect to see Ember become more closely aligned with JavaScript, and for things to become more explicit and less magical • ES2017 features and beyond are going to play a pivotal role in Ember's future
  15. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 DECORATORS Stage 1 https://github.com/wycats/javascript-decorators • for example, you can already use decorators today using rwjblue's excellent addon IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 https://github.com/rwjblue/ember-computed-decorators @computed('first', 'last') name(first, last) { return `${first} ${last}`; } • this is currently a stage 1 proposal, but is incredibly useful for DRYing up your code • the most obvious example is with CPs, as you can see this is much nicer than what we have to do today
  16. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 ASYNC/AWAIT Stage 3 https://github.com/tc39/ecmascript-asyncawait • async/await functions are also on the horizon, and are 1 stage away from becoming included into the spec • these let us write asynchronous operations as if they were synchronous IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 await fillIn('.display-title', 'Hello!'); await click('.update-display-title'); assert.equal(find('.display-title').text(), 'Hello!'); https://github.com/emberjs/rfcs/pull/119 • if you ever been trolled by Promises and async programming, you'll love this new feature • for example, in rwjblue's RFC for making tests great again, he describes how we might make use of async/ await to clean up tests • as a result, code & tests are much easier to read and follow
  17. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 GENERATORS ES2015 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* • generator functions are already a part of es2015 • and are really cool • they allow you to write co-operative code – you can pause, resume and restart them as well as end them early IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 countingTask: task(function* () { this.set('count', 0); while (this.get('count') < 5) { this.incrementProperty('count'); yield timeout(300); } this.set('count', 'DONE!'); }).restartable() https://github.com/machty/ember-concurrency • these are really powerful, and you should definitely checkout Machty's addon called ember-concurrency • it allows you to write Tasks to do things you would have a hard time replicating without generator functions
  18. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 ❤ • as you can see, when you use Ember, you're betting on JavaScript • we're going to become even more closely aligned with the latest JS features, meaning that you'll be able to write more JavaScript and less Ember IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 – Yehuda Katz “All good ideas will eventually end up in Ember.” • i think it's fair to say that ember is the solution to javascript hype fatigue • and that is why i'm excited to give this talk, bc with ember, we don't have to sacrifice performance for productivity
  19. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 THE EMBER WAY • so in this talk, i want to cast away any doubt you might have about what the ember way really is • and hopefully you'll take away some useful ideas you can use straight away in your apps IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 I. Data Down, Actions Up? II. Controllers are Dead III.Declarative & Composable Templates • very broadly, i'll be speaking about patterns and anti- patterns in modern ember apps • first, i want to talk about an important concept
  20. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 I. Data Down, Actions Up? II. Controllers are Dead III.Declarative & Composable Templates • data down actions up is one of the core principles driving modern ember apps • by architecting our applications in this manner, we can build apps that are easier to maintain and reason about IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • data down, actions up describes flow of data thru app • eg this is what app might look like once routable components land • fetch data from API, which gets normalized and pushed into ED store • records then get passed in as attrs to routable component
  21. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • you might have noticed controllers were missing from that diagram • and that's because they're going to be deprecated and removed in the future IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 Is MVC Dead? ☠ • but why remove controllers if they already work? • well, core team found that a component could easily take the place of a controller + view, as components are superior implementations
  22. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 Is MVC Dead? ☠ NOPE • does this mean the death of MVC? • no, but there has been a lot of confusion on this topic, so i'll address it in more detail later IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 2 WAY BINDINGS • what React taught us is that the biggest problem of the frameworks that predate it is the very thing that made them popular • and that is two way bindings • implicit changes are hard to reason about, and • you can very easily cause an infinite cascade of changes if you're not careful
  23. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • when glimmer or angle bracket components land, data bindings will become one way by default • and instead of mutating data, we will have to send actions to update it • this becomes a nice, functional way of building applications that are still reactive • and easier to reason about IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • by ignoring 2 way bindings and instead re-rendering multiple times efficiently, we can have changes propagate immediately without introducing all the cascading semantics of a 2 way bind • and this is made possible with one way data flow and making renders pure and idempotent
  24. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 Who owns the data? • with DDAU, a guiding principle is to ask who owns the data • only the owner should be allowed to modify it • this is particularly important when that data is application state, which we should strive to keep as single sources of truth IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 Component A Component A.1 Component A.2 Component A.3 Route Owns the shared state of A1 - A3 • for example, here's a diagram of what a simple ember app might look like on a given route • lets say Component A only allowed to show 1 child component at a time • perhaps after doing some config in a child component, we need to programmatically show the next one • where would the best place be to do that?
  25. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 Component A Component A.1 Component A.2 Component A.3 Route Owns the shared state of A1 - A3 {{component configComponent user=user changeStep=(action "changeStep")}} • if we follow the DDAU principle, we need to ask ourselves – "Who owns the data?" • In this case, which config component to render is the concern of Component A, it owns the shared state of all its children • so, in order to mutate the current step property, we should send an action up to Component A IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 {{component configComponent user=user currentStep=currentStep}} // don't do this in the child component! export default Component.extend({ actions: { next() { set(this, 'currentStep', 'configure-foo'); } } }); This is bad ☹ • what we must avoid doing is mutating the "currentStep" in the child component • it's a subtle difference, but it means an app that is easier to reason about and maintain • you won't need to spend hours figuring out who is changing what • because you know there is only one place it is being changed
  26. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 …or someone else's state! • but the action is changing the property anyway, why not just do it directly? • when you change the state that someone else owns, you're stealing • effectively it means that the owner no longer controls the data and you have bypassed the owner's interface • however, in the future these problems will likely go away IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 <configure-username user={{user}} currentStep={{currentStep}}> ... </configure-username> • Glimmer Components will be 1 way by default • so even if you tried mutating `currentStep` in the child component, it would not flow up to mutate the currentStep prop of the parent component • unless you explicitly opt-in to doing so with the `mut` helper
  27. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 export default Service.extend({ init() { this._super(...arguments); this.data = []; } }); • another thing to note is that services can also own their own data, • and this is an important part of the Ember programming model • this will be especially useful to know when routable components land, • as singleton state must be moved into a service bc components are stateless IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 I. Data Down, Actions Up? II. Controllers are Dead III.Declarative & Composable Templates • with ddau and routable components coming, what do we do with controllers?
  28. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • so, as i mentioned earlier • controllers aren't completely dead yet… IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 ROUTABLE CONTROLLER • one important thing to note about controllers is that the concept itself isn't going away • however, the implementation is changing • essentially, the concept of a controller decorating a model still exists, but it is implemented as a routable component instead
  29. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 export default Controller.extend({ queryParams: ['category'], category: null }); • you might still need to use a controller right now if you want to use query params and in certain cases of bubbling actions • but these will be moved to the route eventually when routable components land • that said, don't get too creative in trying to avoid controllers, only remove when it makes sense IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 ROUTABLE CONTROLLER • Ember 1.x was a straightforward implementation of MVC, we had a controller and a view • but we've deprecated views in favor of components • and it turns out that what the controller + view was doing could be better handled by a component and service
  30. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 ROUTABLE COMPONENT • so the routable component was introduced • the major difference you have to keep in mind is that a routable component is not a singleton like its controller counterpart • but conceptually, they should do the same things, which is decorate a model IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 Component = Controller + View • a simpler concept is to think of the routable component as the unification of what we call a controller and a view
  31. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 <h1>Alpaca Route Template</h1> <div class="my-amazing-template"> {{#each alpacas as |alpaca|}} <span> I'm a cute little {{alpaca}}! </span> {{/each}} </div> {{foo-alpaca alpacas=alpacas}} move to • there are things you can do today to make this transition easier • the first key thing you can do is to move your route-level template into a top-level component • when routable components land, it basically just moves that invocation into the route implicitly IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 // shopping-cart top-level component export default Component.extend({ shoppingCart: inject.service(), actions: { remove(product) { // ... } } }); {{#each shoppingCart.products as |product|}} <h2>{{product.title}}</h2> <p>{{product.description}}</p> <button {{action "remove" product}}>Remove</button> {{/each}} • the reason it's a good idea to use a top level component now is that it forces you to separate your stateful, singleton logic out and into a service • a transition to a service for the stateful bits and a component for the stateless bits is most likely to be future proof • and will make the transition an easier one
  32. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 Controller Action Action Action Route Action Action Action move to • another thing you can do is to move actions that deal with data to the route • these actions are likely better located in the route as that is where they'll eventually live when routable components land • however, actions that deal with UI or presentational logic should remain on the routable component IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 ACTIONS • there are some gotchas with moving controller actions to your route, so i'll share a way to do so a little later • but first, let's take a look at actions
  33. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 {{foo-bar submit="submit"}} {{foo-bar click=(action "submit")}} • currently, actions can be a little confusing • for example, you could define a classic action on a component by giving it a string property of the action name • or you could use a new style action IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 New-style Actions aka Closure Actions • as a best practice, you should always prefer to use a new-style or closure action
  34. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 {{foo-bar click=(action "submit")}} • a new-style action is essentially just a regular javascript function • which means you can do things like make use of return values, partially apply arguments and more IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 Classic Actions aka String Actions • while classic or string actions are very implicit in nature
  35. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 {{foo-bar submit="submit"}} • and make things hard to debug • for example, it would be easy to mistake submit for a string property • and it would be difficult to trace where the action actually lives IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 this.sendAction('someMysteriousAction', args); • another thing is when you use classic actions you have to use `sendAction` in your components • which is kinda like sending your action to space
  36. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 Your classic action • because you send it up somewhere and hope something is listening to it • who knows where it's going • and the worst part is it never comes back with a return value IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 get(this, 'submit')(...args); • on the other hand, new-style actions are awesome • they're one of my favorite additions to ember and they've been available since 1.13
  37. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • don't let the word new-style or closure action scare you • it's really just a regular javascript function IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 actions: { saveAndDoStuff(item) { get(this, 'save')(item) .then((savedItem) => // do stuff); } } • as i said earlier, you have return values • these are especially useful when dealing with async operations like saving a record • you can return the save promise and then handle it from inside the component • e.g. handle success and failure state
  38. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 ember install ember-route-action-helper https://github.com/DockYard/ember-route-action-helper • so new-style actions are awesome, but out of the box you can only use them if they're defined on the controller as they do not bubble like classic actions • but if you want to move your actions that deal with data to the route this seems rather lame • so rwjblue and i built a little addon you can use today • it's called the ember-route-action-helper IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 {{foo-bar value=value updateFoo=(route-action "updateFoo") }} • by using `route-action` in place of `action` • this lets you have new-style actions that work with routes, and when the time comes, you can search and replace route-action back to action
  39. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 SERVICES • if you recall, controllers without routes can be created, and it was a common practice to manage singleton state with them • a service is a long lived singleton we can use to manage long lived state IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • a great example for a service is a shopping cart or activity feed • for things that have singleton state, you can extract that logic into a service and then inject it into the objects you need to
  40. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 export default Service.extend({ init() { this._super(...arguments); this.cart = []; }, addToCart(item) { // ... }, removeFromCart(item) { // ... } }); • a shopping cart is an excellent example of a service, because there can only be one singleton cart in the application, and it needs to have long lived state across the app • you can implement a cart with as little as setting a property on a service to be an array • or you could do more IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • what we must be careful about is not to abuse services • if your main reason for creating a service is to use it as a global bucket to avoid passing things around, that's a bad sign • instead, adhere to data down actions up and don't use services for something that doesn't need long lived singleton state
  41. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 I. Data Down, Actions Up? II. Controllers are Dead III.Declarative & Composable Templates • now i want to talk about templating, something i'm sure is close to all of our hearts IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 {{if (eq fullName "Jim Bob") "You're the chosen one"}} https://github.com/jmurphyau/ember-truth-helpers • another of my fav new things in Ember is the new Helper impl • introduced in Ember 1.13 and great for expressing presentation logic • for example, this `eq` helper can be found in ember- truth-helpers addon, which we pretty much use for all our apps
  42. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 {{concat "configure-" configName}} http://emberjs.com/api/classes/Ember.Templates.helpers.html • Ember itself also ships with a bunch of useful Helpers like `concat`, `hash` and the`get` helper • the best thing about Helpers is that it basically lets you power up your templates IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 import Ember from 'ember'; export function add([a, b]) { return a + b; } export default Ember.Helper.helper(add); • again, because Ember is converging towards just being JavaScript • a helper is really just a regular JavaScript function • one of the reasons i like helpers is that it forces you to keep your functions small and free of side effects • you can basically create 2 kinds of helpers, this one is a simple one that will recompute every time the params change
  43. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 export default Helper.extend({ // ... }); • for more complex needs, you can also make a class based helper, which is essentially an ember object IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 export default Helper.extend({ localesService: inject.service('locales'), currentLocale: readOnly('localesService.currentLocale'), compute([key]) { let currentLocale = get(this, 'currentLocale'); return get(this, 'localesService').lookup(currentLocale, key); }, localeDidChange: observer('currentLocale', function() { this.recompute(); }) }); • this means you can do things like define CPs, use services and so on • for example this is a helper that looks up translations on a locales service • specifically you might want to listen for a change in locale • and then all the translations should auto-update, even though the locale itself is not passed into the helper
  44. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • some of you might have noticed i used an observer in my previous example • class based helpers are currently the only place it is ok to use one • bc it is still a lightweight implementation (almost mvp), and its lifecycle hooks are not implemented yet • when those become available, we can use them to recompute instead IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 export default Component.extend({ isDropdownDisplayed: false, actions: { saveUserAndHideDropdown(user) { get(this, 'save')(user) .then((user) => { // do stuff set(this, 'isDropdownDisplayed', false); }); } } }); • another reason i like helpers is because it means a lot of UI logic can be moved back to where it belongs • for example, how many times have you written something like this? • this feels a little dirty because it mixes data and UI logic together
  45. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 export default Component.extend({ isDropdownDisplayed: false, actions: { hideDropdown() { set(this, 'isDropdownDisplayed', false); }, saveUser(user) { return get(this, 'save')(user); } } }); • so one thing you could do is to split them up into 2 actions right? • but now you have a problem, because you can't invoke them both • what i'm about to show you demonstrates how powerful helpers can be IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 <button {{action (pipe saveUser hideDropdown) user}}> Save and Close </button> • let's say we had a hypothetical helper called `pipe` • it would let you take the return value from 1 action, and pass it along to the next one • and it would keep passing that value down to each function in the pipe
  46. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 A(B(C(D('E'), 'F'), 'G'), 'H'); • if you've used the programming language elixir before, this pipe helper is essentially the same as the pipe operator • the pipe operator lets you express the above, which isn't very nice to read, as IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 filing = DB.find_customers |> Orders.for_customers |> sales_tax(2016) |> prepare_filing E |> D |> C("F") |> B("G") |> A("H") http://elixir-lang.org/ • a series of data transforms • this is a lot easier to read compared to the 1st example • since we use Elixir a lot at DockYard, it was a language feature we wanted to use in ember
  47. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 <button {{action (pipe addToCart purchase redirectToThankYouPage) item}}> 1-Click Buy </button> • so the pipe helper was born to let you compose small, pure actions to allow more declarative templating • in this e.g. the item argument at the end gets passed into 1st action, then its return value piped into the rest • as you can see, helpers can be really powerful tools to extend templating in your app IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 ember install ember-composable-helpers https://github.com/DockYard/ember-composable-helpers • together with my colleague Marten Schilstra, we've built an addon called ember-composable-helpers • which contains the pipe helper and other declarative helpers
  48. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 <button {{action (pipe save closeDropdown) item}}>Save and Close</button> <button {{action (pipe save quitApp) item}}>Save and Quit</button> • the best part about helpers is composability • helpers can do things that might be more complex to express in your component • using the pipe helper example again, you don't need to define 2 actions called `saveAndClose` and `saveAndQuit`, you can just compose them directly IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 {{#if (eq (not (incr (count user))) (decr (count user))))}} • that said, you shouldn't try to get too ambitious with nesting helpers inside of each other • and when your template looks like this, you're better off creating a CP instead
  49. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • it can be easy to get carried away with using helpers • how much logic you want to have in your templates depends on your comfort level • so use them with caution and exercise best judgement IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • the point is, helpers are better suited for dealing with UI / presentational logic • which quite often is highly abstract and unrelated to the exact data in question • in contrast to CPs which are coupled to data
  50. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • which brings us to our next question • when we do need to perform computation, what is the best way? IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 COMPUTED VS HELPER VS COMPONENT HOOKS • generally speaking, we have 3 ways of doing so • you could express it as a CP, use a helper in the template, or use a component hook to set a value • each approach has its pros and cons, lets look at when we should prefer to use one over the other
  51. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 COMPUTEDS • CPs are one of the first things you learn in Ember • they can range from simple to powerful, and are a nice way of keeping some computation up to date • but these auto updates can be double edged sword • bc observing changes are implicit, so when a CP recomputes, don't always know why it did IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • the best analogy for a CP is an excel spreadsheet • it would be painful to sum a1 and b1 by placing event listeners on the cells and then getting their DOM values • a CP lets you declaratively express logic that changes automatically when the dependents change
  52. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 export default Component.extend({ @computed('payment', 'rate', 'periods') annuity(payment, rate, periods) { let factor = ((1 - Math.pow(1 + rate, -periods)) / rate); return payment * factor; } }); • i would recommend using CPs for expressing business logic • one of the best things about CPs is that they can become reusable macros IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 export function customMacro(dependentKey, ...keys) { return computed(dependentKey, ...keys, { get() { // computed property logic } }); } • you can extract a CP into a macro by creating a function that returns a CP • you can then pass in any arbitrary key or value into the macro,
  53. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 export default Component.extend({ myValue: customMacro('myKey', 'foo', 'bar'), someValue: customMacro('someKey', 'baz', 'qux'), otherValue: customMacro('otherKey', 'meow', 'woof') }); • and then you can import and use it like you would any other macro • this is a great way to DRY up your code and test your business logic IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • another awesome thing about CPs is that they are cached • meaning expensive work is only done when it needs to be done
  54. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 Business logic Reusable ⚛⚛⚛⚛ React to changes Changes can be implicit Totally arbitrary emojis • so basically CPs are great for business logic and have potential for reuse in other ember objects • they update when dependents change, but you can 'subscribe' to these explicitly by specifying keys • however it can be sometimes difficult to trace why a CP recomputes IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 HELPERS • helpers can be simple functions or class based • they don't have a DOM element and are simple to debug – you know where the data is coming from, and what comes out of it • just like CPs, we should strive to keep these pure and free of side effects
  55. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 {{#each (repeat 3) as |nil index|}} <div data-thing={{concat "foo-" index}}> {{!some HTML block}} </div> {{/each}} https://github.com/DockYard/ember-composable-helpers#repeat • helpers are best suited for generic utility functions, and are especially useful for UI logic • for example, this little helper repeats the block inside of it, 3 times • it's non-business logic related but helps us DRY up our templates, so you can use this nice markup instead of copy pasting a block of HTML 3 times IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 {{capitalize "hello"}} {{capitalize "hello"}} • another thing to note about helpers is that ember doesn't guarantee that multiple uses of the helper will actually run at any given point • for example, this capitalize helper will only run once, even though it's used 2x • this is a good thing as it means ember can optimize rendering • but that means helpers must be carefully written to not
  56. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 export function toggle([obj, prop]) { return function() { set(obj, prop, !get(obj, prop)); }; } https://github.com/DockYard/ember-composable-helpers#toggle • you may or may not be surprised to note that helpers can be used as an action if it returns a function • again, this is really useful for expressing UI logic, we might use this toggle helper to toggle a dropdown or a popover IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 <button {{action (toggle this "isExpanded")}}> {{if isExpanded "I am expanded" "I am not"}} </button> • you can then use your action helper like this, • which is much nicer than defining yet another `toggleIsExpanded` action in your components
  57. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 Express UI logic ✍❤ Composable Simple mental model Can be used as action No hooks available (yet) Totally arbitrary emojis • so helpers are great for UI and presentational logic • & inherently composable with sub-expr or nested helpers • they're easy to understand and can also be used as actions • & bc they're still lightweight implementations, we sometimes have to use observers to recompute IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 COMPONENT HOOKS • component hooks are also a nice explicit way of handling computation • for example, when props change from the outside, we can handle it easily • however, they can be quite tricky to use, so you should prefer a CP unless you know why you need to use a lifecycle hook
  58. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 export default Component.extend({ didReceiveAttrs() { this.updateChart(get(this, 'data')); }, updateChart(data) { // update the chart's data } }); • lifecycle hooks are best used when you need side effects, and should be preferred over an observer • for example, you might have a component that wraps a chart library which has some kind of render method • you can use a hook like `didReceiveAttrs` to update the chart whenever new data flows into the component IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 Initial Render init didReceiveAttrs willRender didInsertElement didRender External attr changed didUpdateAttrs didReceiveAttrs willUpdate willRender didUpdate didRender Internal value changed willUpdate willRender didUpdate didRender • this is a simple diagram showing the order of hooks that fire when components render or re-render • when using these hooks, its important to think about whether the changes are 'idempotent' • in other words, these hooks should work no matter how many times they are called • as you cannot easily control the order and timing of the hook
  59. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 export default Component.extend({ didRender() { // this is an infinite loop that will crash your browser let isFoo = get(this, 'isFoo'); set(this, 'isFoo', !isFoo); } }); • for example, this might look like fairly innocent • you might want to toggle some property after a component has rendered • but because it has side effects, it will cause a re-render, which fires the `didRender` hook again, which re- renders, and so on until your browser explodes IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 Dealing with side effects ➿ Can cause infinite loops "Just re-render it" Not invoked in FastBoot environment Totally arbitrary ratings • in a nutshell, lifecycle hooks are useful for replacing observers and for controlling side effects • however, they can also be potential footguns, as you can easily cause an infinite loop or sync issues if your logic is not idempotent • so use them with caution
  60. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 SHOULD I USE AN OBSERVER? • and finally the age old question, should i use an observer??? • this topic has been covered quite in detail already, IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • but basically, you almost never want to use an observer • they're low level primitives used by ember so you don't have to • as i mentioned earlier, the only place it is ok to use one is in a class based helper bc no lifecycle hooks are implemented yet
  61. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 https://youtu.be/vvZEddrClAQ • you should remember that every time you use an observer, Stefan Penner dies a little inside • so don't do it • and watch his Wicked Good Ember talk on observers IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 THE FUTURE • i want to close off the talk by briefly mentioning what we can learn from game renderers • and what the future might hold for how we build web apps
  62. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • game renderers are surprisingly similar to web applications • this diagram shows the rendering architecture for Doom 3 • there is an idea of a "front end" and a "back end" as well as an Intermediate Representation (IR) that sits between IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 Game world state Front End IR Back End DirectX / OpenGL OS Screen • the front end captures the state of the world and determines what contributes to the view • this is then expressed as an IR, which is then passed into the backend where it goes through a hardware abstraction layer like DirectX or OpenGL before reaching your GPU and then your screen
  63. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 Application state Front End HTMLBars Glimmer DOM APIs Browser Screen • an ember app is similar • we capture the state of the app and determine what to render • our templates go through HTMLBars and Glimmer, and then to DOM APIs • this is then taken by your browser and rendered onto your screen IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • so if games and web apps are so similar • how can games that are so much more complex and graphically rich sometimes run faster than a web app?
  64. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 Hidden blocks rendered, CPU time wasted Line of Sight Camera Without occlusion culling • the answer is that games are heavily optimized to make use of your GPU IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 Hidden blocks not rendered, CPU time saved Line of Sight Camera With occlusion culling • & use a bunch of tricks like occlusion culling to optimize rendering performance • i'm really excited to see the trailblazing work Chris Thoburn (@runspired) is doing in this area, you should follow him for updates
  65. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • in fact, Mozilla is actually working on an experimental web renderer for Servo, that aims to draw web content like a modern game engine • early prototypes are able to render at hundreds of FPS IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 https://air.mozilla.org/bay-area-rust-meetup-february-2016/#@1m53s • i won't go into further detail, but you can check out the video for more info • i think web apps have a lot to learn from game rendering, and i'm excited to see us moving in that direction to optimize performance
  66. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 TL;DR • to summarize IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • we looked at the state of javascript and we saw that ember is going to closely align itself to new features from ES2017 and beyond
  67. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 I. Data Down, Actions Up? II. Controllers are Dead III.Declarative & Composable Templates • we then looked at what DDAU means and how to implement it by keeping in mind who the owner of the data is • and to avoid mutating data directly IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 I. Data Down, Actions Up? II. Controllers are Dead III.Declarative & Composable Templates • then, we cleared the air on controllers • routable components and services are superior impl • & will still play the part of a controller, which is to decorate a model • we also looked at using ember-route-action-helper for using route actions in templates
  68. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 I. Data Down, Actions Up? II. Controllers are Dead III.Declarative & Composable Templates • and finally, we looked at ways to power up your handlebars templates • helpers are a great way of performing UI logic in a composable and easily testable way • you can write your own or use ember-composable- helpers to make your templates more declarative IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 • i want to thank dockyard for allowing me to give this talk and being an awesome place to work at
  69. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 • we're organizing the WickedGoodEmber conference this june, so please check out our website for more info IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE & PRODUCTIVITY EMBERCONF 2016 LAUREN TAN SUGARPIRATE_ POTETO • once again, please get in touch if you have any questions
  70. IDIOMATIC EMBER – FINDING THE SWEET SPOT OF PERFORMANCE &

    PRODUCTIVITY EMBERCONF 2016 Thanks! LAUREN TAN SUGARPIRATE_ POTETO • remember to speak to me if you're interested in working AT or WITH DockYard • and i'll give you a sticker too • thank you all so much for listening!