 Hopefully had time with a pee, do whatever you need to do and we're good to go for the next session. Hi, my name is Oli Griffiths. I don't know how to use this remote, so let's do that. There we go. Yes, this is me. I am a software engineer and I don't do JavaScript. I'm actually a PHP engineer. I work at Tumblr. Shout out to my PHP folks over there. Yeah, there's not many in here. Yeah, I work at Tumblr. I've been there for a couple of years now and moved to the US four years ago. Obviously not American or Australian as I usually get. Yes, so I work at Tumblr, I work on a backend team there. And what is Tumblr? Who here has used Tumblr or knows what it is? All right, that's a pretty good, usually I don't get that. Most people are like, yeah, what is it? Like a blogging thing, I don't really know. So this is Tumblr. It's a community-driven application full of all sorts of weird and wonderful things that exist on the internet. It's really centered around communities. It's there to fill the gap between the personalness of Facebook, the public presence of Twitter, and the take a photo of everything I eat land of Instagram. So this is some of the stuff that you can get on there. Whether you're into comics, computers, home improvements, supercars, sports, even food porn. There's pretty much everything on Tumblr for you, go check it out. So, Tumblr, it's a place to be yourself. So cool, that's that spiel out of the way. Let's get on to the cool stuff. Engineering. So at Tumblr, we deal on a massive scale. We've got roughly 147 billion blog posts, 341 million blogs. Equates to about 35 million posts a day and about three million cash hits a second. This is like, it's pretty big data. But along with big data and big infrastructure, comes a set of pitfalls. We have a fairly high latency outside of the US. We are hosted in the US. It's a US brand born out of New York. We have multiple code bases to support. It's a 10 year old application and with 10 years comes a fair amount of legacy code base. And coming with that is an inability to adopt new technologies. It makes it difficult to be agile. And we kind of struggle with our web app to provide native app-like functionality. It becomes difficult. So this is our stack right now. We are primarily in the front end, backbone and jQuery based. We have a custom MVC stack that uses backbone. We have a three to four minute build time, which is super fun. If you're trying to build any of our legacy assets, you can literally go and get a cup of tea whilst that's happening. And I'll do that now. And we have some custom data bootstrapping. So, what am I here to talk to you about? Well, at Tumblr we have a thing called hack days. And if your company doesn't do hack days, you should absolutely give them a try. They're freaking awesome. We love them at Tumblr. They drive innovation and you can basically build whatever you like. You have 24 hours, build anything. One year somebody built an interactive mirror. Nothing to do with Tumblr, but it was cool and it worked. Yeah, so you have 24 hours, you can build anything. We have prizes and raffles and games. At Tumblr, we love them. So, this is me. My first hack day. All bright-eyed and bushy-tailed. Just started at Tumblr and I thought, okay, what am I gonna do? Tumblr has an API. We have a website. We have apps that are powered off the API. We have a different website that's not powered off the API. And it's a completely different code path. So why don't I try building Tumblr as a single-page app? So I decided to have a go with EmberJS. I'd had a tiny little bit of experience with it before and figured, well, it says on the website, it's a framework for ambitious web apps. Tumblr is a web app, so why not give it a go? And this is me. 24 hours in. Burning in copied and pasted HTML and CSS directly into this brand-flashy new application. And it turns out that just copying and pasting your HTML through your current app into a new one, it's a bad idea. Don't do that. But luckily, Tumblr has a facility for us to be able to take hack days beyond a single 24-hour period. We call them labs. So labs allow us a half a day a week to work on a side project outside of our daily roles at Tumblr. And it's encouraged to bootstrap projects that are outside of the regular product roadmap. So I decided, let's start a single-page app lab. And this started in October 2015. So half a day a week, not very long, started October 2015. And I'm gonna preface this by saying everything that you're about to see is still a lab. It's still a complete prototype, but it's proving a point. So this is the ragtag bunch of guys that started this project with me. They are sitting somewhere over there, fellow Tumblers. And we decided the best thing that we should do is to see if we could build a native comparable mobile web app. And what exactly is required in order to be able to do so? So the key point here is making sure that the application that we produce is as close to a native application experience as possible. And you shouldn't leave your web users behind. Yes, provide them with a native app if you so wish to, but don't neglect your web experience just for the sake of it. So we decided we wanted to make a first-class web experience. So this was October 2015. There were really two major candidates at the time. We had Ember, we had React, and we put them head-to-head against one another. We decided these are the two major frameworks that are gonna be able to solve this problem for us, and we decided to build a simple prototype in both. The basic idea of the prototype was to fetch data from the API and render a list of posts. Nothing fancy, just like title, ID, that was it. And what would we need to go through in order to get to that stage? Like from step one, nothing like npm install to that point. What do we have to go through? Can anybody name these two characters, by the way? Yes, and yes, excellent. Now, I don't know if anybody noticed, but Tumblr has this really weird obsession with Guy Fieri. And you'll notice he's right there. Can't get away. So we started, we started down this road, and this is where it started. We jump in head-first into the world of single-page apps. And this means everything from understanding Ember's build system to understanding how to do the same thing in React. We wanted to build a similar prototype in both. So it kind of felt a little bit like Mr. Krabs here with the fast pace of JavaScript. It's kind of overwhelming. There's a lot of things to learn. But we decided, let's do it, feet-first, jump straight in. We noticed that with Ember, we were able to get up and running and be productive from day one. React was a little more difficult. If anybody here has tried to configure Webpack to build React, pre-react create app, which is a God's end, but it didn't exist in October, 2015. And this literally took us days to get just to the point I wish you could do Ember new and have a full setup, build environment tests, and everything. So it was difficult. And because of that, we also looked into the backing data stores that were available for React. And at the time, Redux was still in fairly early stages of adoption. It's kind of difficult to get started with. And Ember for us just worked out of the box. It was simple, Ember new, off we go. And in the first day, we had our list of posts being rendered straight away. So eventually we ended up here. We're aboard the Nebuchadnezzar with immense power beneath our feet. We knew where we were going. We knew what we wanted to do. And we knew that Ember was the tool to do that for us. Ember also has one major, major advantage over React. And I cannot stress this enough. And that'd be add-ons. Add-ons provide so much extra for the React ecosystem that the React, unfortunately, doesn't have right now. NPM is not a replacement for add-ons. It allows you to add into your application through build pipelines, through providing essentially full applications as an add-on. So add-ons really, really allow you to turbocharge your Ember application. Yeah, right? Portland's cool, by the way. I've got another cool slide later, but so creating a native comparable mobile web apps. Like, what is actually involved in that? Like, what things do you actually have to do if you want to be able to build something that is as good as a native app? Well, first off, you have offline support and that was touched on earlier. For a native app experience, you need to have the app boot immediately. It needs to load off the device. You cannot be waiting for round trip times to your data center to go and fetch your HTML and assets and so on. So you really should have the app being bootable off the device. No HTTP requests offline is absolutely paramount. Animations. Users have become accustomed to smooth 60 frames a second animations and they provide this sort of skeumorphic experience to the user where you've got like bounces and easing and so on that feels good when you use them. And I remember when like iOS 7 was released and they added all the gesture support and animations and all that kind of stuff and it made all the apps feel good. The web, lots of good. There are some solutions to these, but out of the box, it has the tools, but there's no out of the box SDKs that are gonna make it nice and buttery smooth for you. Multi-Renewsable, this is something that native apps kind of get out of the box. So you have a UI kit in iOS where you're able to build reusable views that you can drop in anywhere throughout your application. So if you render a post in a list of posts on one screen that you use that same view somewhere else in your application and it looks the same. Users, like they expect consistency throughout your application and for it being modular and reusable within the application itself. So we're talking about like components and things here. That's really, really important. Push notifications. We finally got support for this a few years ago, but prior to that it was like, you shut out a lot, I'm afraid. Push notifications was something only the native apps had. We can do this now and users are expecting this. They expect to be able to have push notifications. Popovers are a really, really interesting thing. Popovers provide in-band context information to your application. So if anybody here has ever used an iPad which I'm sure everybody has, you'll notice that on the iPad you can tap on a button and you can see a popover in line because you have much more screen real estate. Well, on a mobile device, you don't have that screen real estate. You're transitioning to another page. The difference with web is that you're really not limited to the screen size. The user could start out small and then it could get big. So popovers actually become really, really challenging. Touch and gestures. Another really big thing that was introduced with iOS 7 and that everybody has become used to now is that you can swipe to go back and you can do native scrolling and touch and gesture support is something that the native frameworks provide and they provide pretty good SDKs for them. The web, again, slightly more difficult. It's all possible, but none of this is easy. And the last thing is home screen apps. Users expect to be able to have your icon for your app on your home screen. I mean, why wouldn't they? They wanna be able to quickly access your app and you want users to be able to quickly access your app and use it as if it were a native app. It's interesting because studies show that users don't know or care the difference between a native and a web app. Realistically, what is the difference? From the user's point of view, they're clicking on an icon on their home screen and up pops your app. Whether they get that through a web URL, whether they get that through an app store, it doesn't make any difference. And fake news, I made that up. But the sentiment is true. It really doesn't matter where your application comes from. Users expect the same level of functionality from a web app versus a native app, but they honestly don't know or care the difference. If I ask my mom, for example, and sorry, mom, I'm gonna throw you under and bust a little bit here, to describe the difference between a web app and a native app, she'll say that, well, the web app, I get to through a web browser and the native app, I install through an app store or I click on a button on my home screen. But why? Do users even care? Well, I'm pretty sure if they have to download an ATMEG app just to get back to the same place that they were on your website, and they're doing that over a 3G connection, I'm pretty sure they care. So why are these things important? Well, users become to expect a certain level of polish in your application. And right now, web apps don't really live up to that same expectation. So how can we address that? Well, I'm gonna show you some of the things that we have used throughout our journey with Ember. So add-ons. As I mentioned earlier, add-ons really do provide you this extra level of flexibility with your application to be able to use pre-existing made components and utilities and helpers to be able to make your application perform better. So these are the add-ons that we used within our application. We've got everything here from animation support to concurrency, we've got order prefixes so you don't have to keep using your browser prefixes. It'll automatically do those things for you. And these are all community add-ons. These are things that others have made that have made our lives easier. We haven't had to spend the time reinventing that wheel. So the first one is offline support. And we're gonna go back to school for a bit. We're gonna do some maths. So the speed of light, three times 10 to the eight meters a second. The speed of light in glass is two thirds of the speed of light in a vacuum. So you've got a, what's that? 200,000, 200 million meters per second. The distance from the US to Australia is roughly 15,000 kilometers. So speed equals distance over time if you take yourself back to your physics classes. Therefore time equals distance over speed. So if you're in Australia, the round, the single trip from Australia to the US, 75 milliseconds. And obviously it's round trip, 150 milliseconds. You've got 150 milliseconds of nothing. And that's working on the best case scenario that all of the routers and switches between Australia and America are instantly fast, which they're not. Verizon actually publishes statistics on this and they published 152 milliseconds. So I think my maths is right. So offline support, this was touched on earlier. Super, super important. We're using AppCache in here because AppCache has better support across browsers. It's not as clever as ServiceWorker. You really need to do a sort of two-pronged approach there. But you can see here that the app is being put into offline mode and it still works. You can navigate between different pages. Obviously things like authentication do not work. You have to hit your server for that. And this is what it looks like. You've got a manifest here. You basically just list these are all the files. Hey browser, go store all of these offline and the browser will tell you when that's happened. And you can then decide what you want to do when that's happened. You could present the user with a notification saying, hey, how about you add this app to your home screen now that you've downloaded all the assets? So, AppCache, great support, ServiceWorker, not great support, it will happen. It'll be amazing, it'll be great. Animations, animations are a huge part of your application. So we're using an add-on here called Ember LiquidFire. And what LiquidFire provides us is a literal drop-in replacement for your standard outlet tag. And it provides you animations out of the box. You have to do almost nothing to actually support it. It's incredible. And as you can see here, we're logging in. We click login, we'll get a nice little slide animation. Hey, that looks like a native app, cool. So this is what it looks like. You swap out your, let me use the laser, oh yeah. You swap out your regular outlet with a liquid outlet and you right here configure a transition and we're saying from the index route to the login route, we wanna use a crossfade and we wanna use that in the reverse. So if the user clicks forward or back in their browser, they get the reverse. For those of you on this side, we're gonna do a transition from the login route to the dashboard and we're gonna use the two left like the iOS style UI navigation controller transition. Next up, authentication. Anybody here who has an application that has users has to deal with authentication. So how do we do that? Well, we use an application or an add-on called Ember Simple Auth, man, these lasers are cool. Ember Simple Auth, it provides you with scaffolding to handle your application's authentication and session management. It provides the basic hooks to be able to authenticate with the API. It provides the ability to automatically attach outgoing headers so you can attach like a bearer token for your Oauth2 header. You can automatically sign outgoing requests for things like basic auth and so on. And it also provides session management such that if the user logs out in one tab, they're logged out on all tabs. Now, this isn't something that native apps have to deal with. If you log out on a native app, you're logged out. In a web app, the user could have as many tabs open as they want and you want them to be logged out on every single tab. Being logged in in one, when you've logged out on another, that would be bad. Imagine if you were at a web cafe and you'd forgotten to close one of the tabs after you've logged out and now someone has access to all your data. Web Components. So, off the back of Glimmer, we've had this promise of web components for some time and web components is a W3C spec that's still in the process of being finalized, I believe. And as yet, we don't have access to them but we can get very, very close. So we used an add-on called Emma Components CSS and this is a fantastic add-on. Eric Bryn is somewhere in the audience here. Shout out to him, thank you so much. This has made our lives a million times better when it comes to dealing with CSS. Anybody who's had to write CSS will know they've accidentally styled an element that they didn't intend on styling because CSS is cascading style sheet and it cascades down through your browser. So accidentally targeting something else in the browser, I'm sorry, that's a feature, not a bug. This is what it looks like. So you have your styles CSS in your component and the add-on will automatically take those styles and also generate a class name and it prefixes all your styles with that class name. It also adds a class name to your component HTML that's output in the browser. And you'll notice that the class name here matches the class name in the DOM. What this means is it's literally impossible to leak a style outside of a component and this is awesome for that modularity that I was talking about earlier, the ability to be able to reuse components throughout your application and no guaranteed, wherever you put this, it's not going to be affected by anyone else's CSS. Of course, you can still affect child components, which is why we use the descendant selector. Oh, no, which is why I don't know how to use this. Yeah, you'll notice that we're using the descendant selector here so that in the CSS that's generated, we know we're targeting the specific elements that are in our component itself and not accidentally leaking anything further down to any components that are used within our component. And this is what it looks like. So it looks pretty much like that. You can see that, I don't know, you should probably have got the idea from the previous slide, but you can see there that the component name that's generated there is the same as the component name that's generated there. Alert some dialogues. So users are accustomed to your application providing alerts and dialogues either natively or within the application itself. So we use the Ember add-on called Ember CLI Flash and this is what it looks like. As you can see, error. Thing did not work. We also color the header bar here up here, the toolbar with the Ember computer property that just changes the style there. You'll notice that when this one disappears, that goes green, pretty cool. And you can have sticky messages, which is pretty awesome, so you can stay up there till the user dismisses it. The user dismissed it. We also extended this add-on to add dialogue support. So you can automatically add dialogue messages, of course. To your application, this is in line with our standard branding. Our native apps do this. Our web app does this as well. And it provides a pretty simple way of providing flash messages to the user. So we use this add-on Ember CLI Flash. Shout out to Lauren Tan wherever she is. Great work on this. This has really saved us a lot of time with providing a centralized way of managing flash messages. So if I go through this very quickly, you inject your service, the flash message service. You grab the service and you push a message onto it. So this is like a notice. And this can be anywhere in your application. Let's say, for example, you have some offline notification message. We then built a component, which outputs the flash messages that are on the queue and the flash message system itself handles all the expiry of the messages, cleaning them up, making them disappear, and so on. Really, really awesome. It just worked out of the box. Well, we had to do a styler, which was pretty cool. Concurrency. There's a talk on this, I think, tomorrow by Alex. Concurrency is a fun thing. If you've ever done a callback with an Ember component, you will know you get into problems if that component is removed from the DOM. So Ember concurrency helps with that. To be here, we've got a like button. And you can see, I'm clicking the like button, and you can see on the right-hand side here the status messages that I just hacked in there. And you can notice that, every now and then, we've got a bunch of grouped up messages here. So every time we click this, we're outputting a console log, and then from the action that's triggered, we're also doing a console log. And you'll notice that, although you can't see it, I'm clicking this button furiously right now. And outputting here, we've got multiple stacked messages because it's not doing anything. The actual main action itself that's doing the HTTP request, only one of them is happening per full run of this button. So that timeout for that animation to happen and then disappear, we only allow one action to actually happen whilst that's running. Let me show you what that looks like. So this might be, I believe this is easy enough to see. We import task and we import timeout from Ember concurrency. And we define a property on our component. And what this does is we call the task function and we provide a JavaScript generator. From here, I'm just calling the action data down, actions up, this is good. So our action is gonna be called on this like button and that'll be handled further up the stack. And then we're yielding a timeout here. So what this does is this yield and this timeout is actually yielding a promise. And the task system in Ember concurrency is expecting to receive a promise, return from it, and it will pause that, it will pause this function whilst that promise is running. So timeout is just a really simple wrapper around set timeout that returns a promise. And notice we've got this drop keyword on the end here. There are various different ways to configure this and we can forget it in the drop method. Basically what this does is every time this action is tried to be fired, if there's one already running, it'll just drop the existing ones. You can do other things like stack them up, you can cancel existing ones and so on, but this is the functionality that we wanted. And then from our click handler, we just call perform on the task itself there. Super simple and it means we don't end up in this callback hell nested callbacks or chained then statements. It makes our code very, very declarative and very easy to follow along. So next up, popovers. As I mentioned, popovers are really, really difficult with responsive applications. You have no way of knowing realistically whether or not you should be transitioning to a new route and showing a different screen or whether you should be showing the context information as a popover. So we actually built an in repo add-on to handle this for us and this is what it looks like. So we've got our account menu up here. You click on that, we show a popover and we have logic in the popover to make sure it stays on the screen and doesn't overflow the edges and so on. And if you change the size of the viewport and you go down to mobile size, this now actually does a route transition instead of doing a popover. And it turns out this is actually a lot more complicated than it needs to be. Doing this in a nice reusable way has meant a little bit of hacking, but we're using mostly public APIs to do it. So it actually works really, really well and I don't actually have a code example of this, but if anybody wants to see it, please come and give me a shout afterwards. And you basically invoke this in the same way that you do a link to. We created a link to popover helper and that basically wraps around the popover functionality and it automatically pulls your model from your route hook. So all you have to do is define what is the route, what is the component that I want to render within the popover itself and that's it. It hooks up everything else for you. It'll grab your model, it'll inject it into the component as if by magic. API actions. So who here has used an API that is not a standard REST JSON API? I'm expecting to see every single person's hand up in here. I call these restless APIs. It's all well and groovy if you have the perfect RESTful JSON API and Ember data is like, cool, bro, I got you back. If you don't, it's not so easy. So our application has some restless end points, namely like that like action you saw earlier is sending an HTTP request to user slash like and you can see it happening here and user slash unlike and Ember data doesn't handle this very well at all. In fact, it doesn't even cover this in any way you are on your own. However, Ember data handles like getting the URLs to send HTTP requests to, there is some crossover there but it just doesn't handle the non-RESTful semantics. So we built an add-on to handle this for us. We call it Ember data actions. It's currently in repo. I'm hoping to open source it but this is what it looks like and it's super, super simple. So, oh, nope. So here, I haven't imported it up here so you can't see that but basically you call action. So you define a property on your model, call action, pass it the action that you wanna run and you pass it the property that you want to toggle or change when the action has returned true. Same in the inverse, unlike, unlike and we're gonna change liked back to false. Then in your adapter, you define a set of actions and here I'm just defining the like action and I'm gonna say when this action is fired, hit up user slash like and you're gonna take the model keys, ID and reblog key. These are Tumblr specific things and those will automatically go in the post body. You can configure whether it's a post request, a put request, delete request, whatever you like. And those things automatically get added into the body. Then in your component, the API is super, super simple. Grab the post, call like. Seems simple, right? This is really, really difficult with Ember data out of the box. So we built an add-on for it and we did it as an in repo add-on. In repo add-ons are super cool. You should give them a try. This is what this intax looked like to generate one. You call ember g in repo add-on and you call give it a name and then when you want to generate something within that component, you want to say create a component in that add-on. You just add the dash dash in repo add-on and give the name of the add-on that you're generating the component into and Ember will automatically generate you a structure that looks something like this. This is our ember data add-ons. Very, very simple. We've got our mix-in for our adapter and we've got a utility that we use in the model, that action callback that I showed you. Really simple way of prototyping out something that you think maybe I might want to make and source this in the future. Last thing is living style guides. Now living style guides has been really, really useful for us and we're using an add-on called Ember freestyle. And as you can see here, this kind of looks like what you saw earlier. We've got all our different add-ons. It shows you the invocation below it. You see right there. And this is actually rendering what is in here. So you basically wrap this in a special semantics for this add-on and it will render that add-on in that block and it will show you the invocation at the bottom here. This is really useful for both developers and for designers because it allows them to separate those concerns. Designers can decide, well actually, you know what? I don't want to see all the code. I don't really care about that. They can toggle that on and off. Developers can look at this and it basically provides you a certain level of API documentation for your components. You can see right here, it lists out all the different permutations of a component that you might have. And bam, you can happily manage change management, make sure things don't break. And as you can see, it looks pretty snazzy. So we still have plenty more to do. And one thing that I really want to get across here is the use of add-ons allows you to do one great thing. It allows you to stop reinventing the wheel. Unless of course you're in the business of wheel making, then don't go ahead and reinvent the wheel. Build an add-on, use an add-on. At the very least, if you have a concern in your application, it probably applies to someone else as well. So consider making an add-on. Start off as an in repo add-on and move on from there. So things are not perfect right now in the Ember ecosystem. Ember data is awesome if your API is a perfect RESTful JSON API. Rocket.js, it's super powerful. The documentation isn't great. Ember works out of the box, but sometimes you don't want the whole box. And mobile performance for us in this application is absolutely paramount for 2017. And I know it's something that is the forefront of Tom's mind right now, making sure that Ember really is a first-class citizen when it comes to performance. So the question is, how do we get from here to here? Well, I was just trolling around on Twitter and I found this guy and he said this, as a tech community, we must treat documentation, marketing, logistics, infrastructure, art, et cetera, work with as much respect as engineering and this couldn't be any more true. We have a responsibility as consumers of this application to give back. Now that doesn't mean you have to develop an add-on. It could be that it took you weeks to understand what serializers are and what adapters are in Ember data and then finally read a blog post somewhere, and you're like, oh my God, I get it. Submit a pull request to the guides repo because it's not that clear. Another thing, when you're looking at, this is really interesting, and I probably should have done this really prior to this because I keep my own dog food. Returning something from a model hook, when you look at how that stuff works in Ember, it's just assumed that you're using Ember data. Maybe you're not. It doesn't actually say on the first model, the guides for the model hook that you can just return a promise. Or if it is there, it's very, very buried. And these are the kind of things that stop other developers from being able to get involved in the Ember ecosystem because some people want to persist and really dig into the internals and find out how stuff works and some people don't. For those that don't, that's totally okay. No one's expecting you to do so. But if you know the answer, just submit a pull request, make things better. Another big thing we need to be doing is we need to break up the monolith. And I was really unsure about this slide because it's kind of weird and it creeps me out. But for those of you that haven't read it, there's an RFC, it's up here, it's number 176. And you can get to it, github slash mjs slash rfcs about breaking up the Ember monolith application. Glimmer is going a huge way to accomplishing this. There's a whole bunch of other things within Ember itself that if you don't need all of the box, don't ship all of the box. And that's imperative not just from a shipping the bytes down the wire, but from the bigger the payload, the longer it takes, the larger the payload, the more JavaScript there is to pass and evaluate and the slower your boot up time is gonna be. It also means that we can really start competing in the framework wars as not shipping a monolith. I think people nowadays in the JavaScript community are used to being able to receive small packages. So that's what we wanna be able to do. So what did we learn? Well, it turns out making web apps, it's hard. But making native comparable web apps is even harder. There are so many things that you have to take account of these days. We don't have the luxury of native applications where they come with these lovely APIs and SDKs like iOS that comes with UI kit and all the animation libraries. And we don't have that, we have the DOM. If we're lucky, maybe somebody has abstracted it into a library that we can use. This is where add-ons come in. We are providing abstractions on top of this to make these APIs accessible and consumable by other developers like yourself. You can't simply just get by with jQuery anymore. These are some of the concerns that you have to deal with if you're building a mobile web application that you wanna be as good as native. There's a lot of things here and I've covered some of them but there's a whole bunch of other things even outside of this slide as well that you have to be aware of. Like security is a huge thing. So things like sub-resource integrity, go check that out. It's supported in Ember out of the box. Isn't that cool? Things like cross-site scripting. You wanna have 60 frames a second animations. You wanna make sure that you have code splitting which we hopefully will do sometime in the future. You wanna have a deployment strategy. You need a build environment. You wanna potentially have offline data and use it like index DB. You wanna have async bundles so you can split your application down into smaller pieces and only ship the user what they need. So many things that you have to deal with. Don't try and reinvent the wheel. So I'm gonna leave you with a few things here. Number one, stand on the shoulders of giants. You can do it by using a framework like Ember. You don't need to reinvent the wheel. You can use the collective knowledge of the community and we can all benefit together. We can do great things. If we work together, build add-ons, as the core team said earlier as Tom and Yehuda said, they're gonna be focusing on lower level primitives which will really allow add-on developers to bootstrap and build out functionality on top of Ember for the entire community going forwards. So let's say check out emberadons.com, great resource for the latest and greatest add-ons that are on the market. And I wanna take this opportunity to give a shout out to Tom and Yehuda and the entire Ember team and the core teams for building an incredible framework that we've been able to build our applications on top of. So I think at this point, I'm just gonna give everybody a round of applause for the Ember core teams. So thank you. This is me. If you wanna hit me up, please do so. If anybody wants to have a look at the application that we've built, I'm more than happy to demo it. And I'll leave you with one tragic final thought. Thanks very much. Enjoy your break. Thank you.