The evolution of Ember at Intercom
It’s one of those facts of startup life that is easy to overlook, but the success and growth of companies can be deeply intertwined with the technology they use in their stack.
In this post, adapted from a talk I gave at JSDayIE conference in Dublin last year, I’d like to explain how that has been the case with Intercom and Ember.
Period of growth
Ember was released eight years ago. We’ve been using it for about six years in Intercom – and I joined Intercom six years ago. Intercom is a really amazing company, by far the greatest bunch of people I’ve ever worked with.
I guess the biggest impact I had, certainly in the early days of Intercom, was introducing Ember and convincing people that we should take a bet on it. Here’s the story of how our use of Ember has changed as it has evolved in that time.
“Our growth is not just in our office, but growth in customers, revenue, people, dinners, lunches, lines of code, bugs, features, tests, everything”
A lot has changed in the past six years across the board, but looking at technology, the web, JavaScript, there has been huge change. Intercom has changed massively too. Eight years ago it was founded by four Irish co-founders keen to build something that would help people like them, product people, connect with their customers in a more personal way. And we’ve grown massively in those eight years – so now we’re 600 people, five offices and we’re valued at more than a billion dollars. When I joined, we had this small office on Dublin’s Camden Street, with a little kitchen, a few desk pods and three little meeting rooms.
Our growth is not just in our office, but growth in customers, revenue, people, dinners, lunches, lines of code, bugs, features, tests, everything.
The web has also gone through massive change. For example, we no longer need jQuery. There was a time when you couldn’t do anything serious without jQuery. The nonstandard browsers have fallen away and we’re in a much better place at the moment. Frameworks have drastically improved the capabilities of small teams to produce amazing experiences for people in very short periods of time. JavaScript has also evolved massively and incrementally.
Classes, modules, const, let, maps, sets, promises, proxies, async/await, generators, so much more. It’s evolved from being a toy language maybe 15 years ago to a really serious and productive language. And compilers like TypeScript and Babel build on top of that to allow experimentation and adoption of potential features a little earlier. And it’s a really healthy ecosystem at the moment. I don’t think I’ve ever experienced JavaScript fatigue, but it’s something that no doubt lots of people have. It’s hard to navigate this change.
Principles of change
I want to take a quick look at how Intercom manages change and then take a technical look at some of Ember’s approaches to technical change. We have a set of shared principles at Intercom and these help guide us as we decide which direction to go in and how we get there. The top three are R&D principles:
Then there are design principles. They’re kind of derived from the top three.
Then we have our engineering principles.
The important thing about these principles is they’re not truisms. You could flip any of these or all of them and quite likely build a successful company. You would just have different principles. These are ours though, and they help us all keep aligned.
When I compare these with Ember’s own principles, I see a huge overlap. I don’t think this is by accident as some of the things that attracted me to Ember seven or eight years ago are the same things that attracted me to Intercom and continue to as I love working there. For example, both Ember and Intercom take on ambitious goals and aim to get there, and to figure out where there is, by delivering a continuous stream of small incremental changes. This is a slide from a talk that I gave in 2015. It shows the frequency at the time at which Intercom shipped to production each day.
So right from the very early days in Intercom, continuous shipping was a key part of our culture, and it remains the same today. This is where we introduced Ember.
From the same talk, this is a timeline that shows how productive we were at once after introducing Ember and incrementally migrating away from a Rails jQuery front-end. And at the time we significantly raised the bar of quality of what was possible in our product. And this was a period of huge growth as our customers love these features and in the years after we’ve kicked on. At the time I thought this was really impressive. Now I think it’s somewhat impressive. It’s pretty easy to move fast as you know, when everything’s new and shiny.
Moving fast as you grow
The real test is can you continue to move faster or faster when your app is many years old and when the company is much bigger. There’s a natural tendency for everything as it gets bigger to become more complicated and for things to slow down. If you check out our changes page, you can see every significant feature we’ve released – it’s just this continuous stream of features, it just goes on and on. It’s relentless. I hope this never ends, it’s the heartbeat of our company.
“Ember’s strong conventions means that there’s usually a right way of doing things on the happy path. This helps new people who join get up to speed quite quickly”
As long as I can remember, we’ve had this tradition where new engineers who join Intercom ship something to production on their first day and ship a feature in their first week. This is obviously a challenge for people joining. It’s a new company, you want to do well, and in general people tend to nail it. But I think it’s actually a bigger challenge for Intercom, as we continue to grow, to enable people to be able to do this. We have a small but really great infrastructure team who help maintain fast development environments. We use fast remote machines to run our dev environments, keep tests fast, deployment times fast. But also, Ember’s strong conventions means that there’s usually a right way of doing things on the happy path. This helps new people who join get up to speed quite quickly.
So I haven’t really talked about how we deal with this constant change in the web platform in JavaScript, just about how we ship. The truth is we really don’t have that problem. Ember acts as an insulator to all of this churn. Here’s a timeline of Ember releases:
Ember started as SproutCore, and Ember 1 was released in 2013. It follows a six-week release cycle, similar to Chrome and Rust. This is the “ship every day” for a framework. Six weeks is about the right duration. And here’s the same timeline with our Ember upgrades in pink:
Because these releases are so frequent, upgrading from one version to the next is relatively simple, and we don’t tend to spend much time upgrading. You can see the three versions, Ember 1, Ember 2 and Ember 3.
Ember follows SemVer strictly, so it never breaks backward compatibility. It never makes a breaking change in a minor point release. Instead, it will deprecate things and when we get to a point where a new version is released, all those deprecations will fall away. There’s a slight challenge with that in that you release Ember 2 or Ember 3 and you hit Hacker News, and it’s like “What’s the new features?” And you say, “There’s no features. That’s the point.” That doesn’t make for a very good marketing story. But that is actually what happens. There are no new features in major releases. The features land incrementally in minor point releases. And this is where Ember Octane comes in, which I’ll talk about soon.
Preferred choice
I gave a talk in 2015 in our old offices when we were still running Ember 1. I was speaking about a drum machine that I built in Ember. In the Q&A at the end two people asked me almost the same question. “Why don’t you use Angular?”
Angular at the time was the obvious choice. React wasn’t really on people’s minds. It was in early betas and Angular was backed by Google. “It’s more popular, it must be better”. I didn’t have a solid answer at the time. I liked Ember because I trusted Tom Dale and Yehuda Katz, the co-creators of Ember. And I agreed with their philosophy of chasing ambitious dreams and getting there incrementally.
“Breaking compatibility with Angular 1 would have drastically changed Intercom’s trajectory, and it would’ve cost us millions of dollars”
When Angular 2 released, however, and broke compatibility with Angular 1, that really drove home how lucky we were to choose Ember. I don’t mean to give out about Angular – I haven’t used it that much, I don’t know too much about it. I don’t really believe in framework wars, I don’t think it’s a good way of framing the reality – it’s not a zero-sum game. Obviously, all frameworks cross pollinate – Ember has learned a huge amount from React for example.
But I’m sure that their approach of breaking compatibility with Angular 1 would have drastically changed Intercom’s trajectory, and it would’ve cost us millions of dollars. It would’ve cost us months and months of lost opportunity.
This would never happen with Ember. Any big change in Ember has to happen through consensus. There’s no top-down Facebook or Google, which can be both a strength and a weakness. But everything is by consensus. Even changes that the core team want to make, even if Yehuda wants to make a change, it needs to go through an RFC process, and it needs to have broad consensus. Ember’s commitment to backward compatibility gave us a bridge to the future then. And it continues to give us a bridge to the future as it evolves.
Ember’s evolution
I want to dive into two technical areas and show how Ember evolves. The first is Ember’s rendering engine, which began as something quite primitive. But it has evolved into what I consider the best-in-class rendering engine of all the frameworks and libraries that I know the technical details of.
Ember templates are a declarative programming language built upon HTML. It follows the rule of least power, which means that unlike a powerful language like JavaScript, templates can be easily and fully analyzed at build time without having to execute them. And this has allowed Ember’s rendering engine to change significantly, at least three times. Each time the build time template compiler becomes more sophisticated, and the rendering engine that runs the compilation more and more performant.
“We didn’t have to change any code – you just upgrade, and you benefit from the new compilation and runtime”
So the first approach was very primitive in Ember 1. It was from Handlebars. Ember templates would be compiled into simple functions which would concatenate strings which would be run when you wanted to render something, pass it over to the browser and the browser would inner HTML it, converting it into DOM. This was good enough for the early days, and Ember was clever about avoiding rerenders and performance was okay. But when you did have to rerender a lot of things, performance really suffered. When React came out that was just so noticeable. You might remember the dbmon examples where there were lots of flashing green boxes and Ember was just terrible at that particular benchmark. It was the rise of React and its approach, with its virtual DOM and using DOM diffing as a strategy, which showed how much faster rendering could be.
So Ember’s second go, which came in 1.13, was a drop-in replacement. We didn’t have to change any code – you just upgrade, and you benefit from the new compilation and runtime. This was designed to fix the rerendering performance problems. And the same Ember templates were compiled into these functions this time.
This approach did drastically improve rerender times, but initial render suffered a little – mainly because the templates were compiled into JavaScript, and were quite large. And the browser has to do a lot of work to parse JavaScript. It’s obviously a complex language. And a large part of Ember apps are templates. Which brings us to the third and current strategy, which is Glimmer, which landed in Ember 2.10 maybe two and a half years ago or so.
Glimmer arrives
Glimmer treats templates and the whole rendering engine as though it were a programming language, and there’s a VM for running the compiled code. So taking this very same template, we compile into a data structure – it’s got integers and arrays and string literals, and it outputs as JSON, so parsing is very quick. It’s also compact in size.
This is what’s delivered to the browser as part of the app bundle. When loaded, these opcodes represent a linearized set of instructions. So we take those instructions and we execute them on top of this light VM, the Glimmer VM.
“It is quite amazing that Glimmer can do this when it isn’t designed to be something like Preact”
So we have an instruction pointer which runs sequentially through the opcodes. So the first instruction is to build a div element. Then we move on and run the static-attr opcode, which sets the role attribute. And then do another one for class active. And in this case we’re done. So we flush the element and stick it in the DOM. This is a very simple example because it’s completely static. But what about something more dynamic?
Well, the Glimmer runtime is actually made up of two VMs. One is optimized for constructing the DOM, on initial render, and one is optimized for updating the DOM when the data model that backs the template changes. So here’s a template which has some dynamic content. We could just use the previous program, the Glimmer set of instructions to rerender. But we’d be doing a lot of work for this static stuff that we know is not going to change. So the way that Glimmer achieves this is through a technique called partial evaluation. It simply means that we generate an optimized program by evaluating an initial program.
So here are the opcodes for this dynamic template.
There are two new ones there, static-content and dynamic-content. So, let’s run it as before, create a div and set a class attribute. This time we’ll construct a text node and insert it, that’s static. Now we’ll construct another text node, grab the value of person.firstName and insert that into DOM.
In addition to that though, we’ll also emit a new opcode, which is for the updating VM. And we can be clever here. We have a reference to the text node, we know what the previous value is, and we know where to get the data should we wish to check if it has changed. We then flush the element as before. So now when the data does change, all we have to do is evaluate this very simple program in the updating VM. It’s a little more complex than this, but essentially it compares values and if they’ve changed it will run the opcode which will update the text node.
And this turns out to be extremely fast. LinkedIn did an experiment running Glimmer JS alongside Preact, which is a lightweight alternative to React which focuses on raw render speed, and they found the two in both in-the-browser and server-side, and also in initial render and in updates, to be virtually neck and neck. It is quite amazing that Glimmer can do this when it isn’t designed to be something like Preact. The Glimmer VM is one of my favorite projects. It looks like it’s just going to continue to yield improvements in the coming years.
Enter Octane
Ember Octane is a set of modern Ember features that have landed incrementally over the last year. And in six weeks time they will all have landed and everything will be polished and this will be the new Ember edition.
With Octane, we have a new @tracked decorator where we can say this is something that can change and if this data does change, you should update anything that uses it automatically.
Here is a model, it’s just a class with three properties that we want to track – first name, last name, and the person’s birth year. Then we have two getters, which simply use JavaScript to return the values. We have a function called randomize, which sets a year in the 20th century. And then we have a component, which renders one of these people. The button {{on “click”}} has the same API as addEventListener. So, that’s easy to understand. And when we click that, it calls the randomize function. And we can render the components three times. And amazingly that’s it.
I don’t know what you would remove from that. I don’t know what’s there that’s bloat. Everything has a purpose, and it’s beautifully minimal and clean, and this is why I’m very excited about Ember.