Ember conventions helped align the team around best practices and prevented bikeshedding over every decision. Need a build tool? Install Ember CLI and run
ember new my-app
Need a first-class router? You don’t have to think about it, it’s there. Want to deploy an app in 10 minutes? Ember CLI Deploy. Ember helps prevent gridlock over picking these dependencies, allowing you to focus on features. It gets a lot of things right.
Scaling Ember (Performance is hard)
Over time, as with any large application, we started to exercise Ember in ways that are well beyond the “getting started” guide. We fought through issues with rendering static content for search engines and social networks. We worked through the upgrade to Ember 1.13 and moving the app to Ember CLI.
As our application grew, we added more and more ambitious features. This led us to leverage the core abstraction in Ember for creating UI, the component. A component in Ember is great, and it easily composes as you create your app.
The problem, however, is that this abstraction is slow. Not mind-bendingly slow, but slow enough that it becomes noticeable to users once you start rendering a non-trivial number of them.
I added viewport-aware rendering. I swapped link-to for href-to in performance-critical components. I inlined a variety of heavily used components rendered in loops. The list expands from there. Eliminating most of the low hanging fruit, there was only so much we could do at the application level to improve the framework boot time, improve rendering, or improve Ember Data serialization time.
Our app performance is respectable on modern desktop platforms, but I wish we could hit some numbers on mobile—and we basically can’t. (We did not go so far as to reach for a custom renderer.)
Enter Code Splitting
On the hunt for further performance improvements, I started exploring an idea called code splitting. Code splitting is the idea of breaking up an application into chunks so that initial render only loads what the current page depends on. Almost all applications will grow, not shrink, over time. Code splitting prevents the natural balloon of your app bundle which will slow your app down if you are not paying attention.
The term code splitting was pioneered by Webpack, the module bundler and build tool. This was a natural place to start. I watched Pete Hunt’s talk on how Instagram works and thought, “this is exactly what I want.” When I started to really dive into Webpack, it blew me away. Webpack’s architecture is centered around explicitly imported dependencies and represents your application as a dependency graph of modules. Because of this, implementing code splitting is almost entirely done by Webpack because it can reason about each split point’s dependencies when building your actual bundles.
When I explored implementing this concept in the context of an Ember and Ember CLI application, I hit several blockers that made it near impossible. It’s true that Ember CLI gives you a fantastic getting started experience. But that experience isn’t free. The cost of this abstraction is a limitation on your flexibility to change. It’s impossible, for example, to replace Broccoli with Webpack without reimplementing most of Ember CLI. This is a daunting task that could mean giving up on Ember Addons and other useful features. That is a hard pill to swallow.
Around this time, the elephant in the room, React, was starting to get too big to ignore. I’d done some basic experimentation and found the API elegant, but hadn’t explored deeper than that. With the momentum of the community and projects like React Native, this was something I wanted to explore more. I started building a React application in order to see how the tools worked together.
I skipped the boilerplates (although some of them were very informative) and started with a basic project including React and Webpack. I had gotten used to Ember’s large API surface. From templates to Ember.Array, Ember includes almost everything out of the box.
Much as with Ember, the React ecosystem had most of the basics covered. But the React tools are further along. As my application grew, I added Redux for state management and React Router for routing. Redux provided a small API for predictably managing the application state. It enables amazing developer tools with features like time travel. React Router and Webpack helped me solve hard problems like code splitting and server-side rendering. I’ve been trying to solve these problems in Ember for a long time.
Maybe Ember Fastboot will deliver on the promise of simple server-rendering and rehydration. It’s also possible that Ember Engines will allow me to split my app into chunks in the future. But the reality is, if you want to ship these features today, React will take you farther.
It’s a trade-off, though. Ember is stable. React is fast-moving. Sometimes I miss that stability.
Confessing My Ember Guilt
Learning these new tools made me realize something. It’s no secret that we can only fit so much in our head at a given time. Programming is no different. When it comes to programming languages, frameworks, and libraries, we have to be picky about what we invest our time in learning. What we learn will be what we remember, and what we remember will be what we use. Because of this, I’ve become a big believer that APIs should be as minimal as possible. If I’m going to spend time learning something, the benefits must outweigh the effort to learn and retain that knowledge.
React, on the other hand, is just a small (but powerful) piece of the puzzle. The surrounding community has brought us Webpack, React Router, Redux, and other tools that have enabled powerful applications.
There’s room for both.