Coauthors: Chad Hietala and Sarah Clatterbuck

At LinkedIn, we use Ember.js as our client-side web application framework. This is part of the larger Pemberly architecture we employ. Historically, native applications have had a large advantage over web applications in terms of not only technical capabilities, but also in their ability to drive engagement due to extremely rich SDKs. That being said, this environment has changed pretty drastically over the past five years, as the browser has evolved from a document viewer into the world’s widest distributed application runtime. Below are some examples of functionality that one would have associated with a native experience five years ago that are now available to the web platform:

Ember’s goal is to be an SDK for the web that ties together these low-level APIs into a more productive developer experience that leads developers down a path of success. Having an opinionated tool chain not only allows you to scale up, but also to be able to completely overhaul pieces of infrastructure in a backward-compatible way without impacting application code.

By default, Ember applications construct their UIs on a user's computer instead of utilizing a string of HTML from the server. While this is done to allow for very interactive applications post-initial render, you have to deal with reality: users on the web are network-bound, CPU-bound, and memory-bound. Because of this, app developers must pay attention to not only the payload size, but also the parse/compile and execution time. Having a framework with an opinionated architecture across applications allows us to address performance related bottlenecks in a structured manner and optimize the system holistically.

Last year, we contributed to an evolution of Ember’s rendering engine to drastically reduce payload size, CPU time, and memory pressure to further our efforts to provide a delightful browsing experience for our flagship mobile web and desktop web apps. While the team has also been working on Fastboot, a means of server-side rendering Ember applications, we still needed to optimize the runtime for future work we would like to do. Our team at LinkedIn specifically contributed to both to the development and the integration of a ground up re-write of the rendering engine under the Glimmer2 code name, now simply known as Glimmer.

Originally, the first iteration of Glimmer; now known as Glimmer1, was a thin layer that sat on top of HTMLBars. In 2016, at EmberConf, the next generation of Glimmer was announced as a ground-up rewrite that would incorporate learnings and realizations from the past five years of client-side rendering to increase the performance in this area of the framework. These learnings including:

Components should become first-class primitives of the rendering engine, allowing for runtime optimizations. Templates are just a declarative way of describing a UI and because of this, they have properties like referential transparency. Templates have time-varying values, making them essentially functional reactive programs (FRP) that you can re-run to update the values and produce a new UI. Instead of push-based semantics for updating the template, the system can be modeled as a discrete pull-based system with no notion of observers or notifications. Since we are effectively designing a programming language and the underlying runtime, we should architect it using well-standing tenets in programing language implementation, such as JIT Compilers and bytecode interpreters. Having a VM architecture would allow us to more easily implement well-known optimizations such as constant folding, inlining, macro expansion, etc. Since the project would be sufficiently complex, we wanted a first-class type system. For this we chose to write Glimmer in TypeScript. In addition to making a project more maintainable, they also enforce object shape, which is an heuristic several JavaScript engines use to optimize.

With this design considerations in mind, we first had to change how templates were compiled to drastically reduce the size of code we send to a user’s browser. This involved changing the compilation stack that occurs during the build of an Ember application.

Ahead-of-Time (AoT) stack

This part of the Glimmer architecture has a lot of similar parts to the Glimmer1/HTMLBars pre-compilation stack in that it uses the Handlebars parser and spec compliant HTML Parser and Tokenizer to produce a combined Abstract Syntax Tree (AST), which is consumed by the JavaScript compiler. While the majority of the AoT Compiler stack is the same as its predecessor, it differs in that it produces a JSON structure known as the wire format instead of an executable JavaScript program. Below is a high level diagram of how the stack works.