5 Things to Know About Ember 2.10 and Glimmer 2
Wow! This takes me back! Please check the date this post was authored, as it may no longer be relevant in a modern context.
Today marks the release of Ember.js 2.10, a stable release of Ember that includes a major redesign of Ember’s rendering engine. We call the new rendering engine “Glimmer 2”, as it began by building on the Glimmer 1 engine that shipped in Ember 1.13. The community is really excited about this release. For those who haven’t been keeping track of the play-by-play in Ember-land, here is a list of five things you should know about the change.
1. Ember 2.10 and Glimmer 2 are Drop-in Upgrades
The Ember project is committed to semantic versioning and well-designed upgrade paths for application code. Because of this, we have a lot of mature apps in the ecosystem. For example Travis just shared this post on how their app, started in 2011, is still moving ahead in 2016: Tomster and Travis, sitting in a tree: Ember at Travis CI.
Despite Glimmer 2 being a big change at the level of Ember internals, the upgrade diff for many apps should look like this:
// bower.json
"dependencies": {
- "ember": "2.9.1",
+ "ember": "2.10.0",
"ember-cli-shims": "0.1.3",
And that is pretty amazing. This isn’t just theoretically so simple either, it is actually that simple. Bigger apps will likely need to upgrade other addons that depended on internals such as ember-wormhole, but this release is exceedingly well tested in Ember’s own test suite and in real apps that have experimented with the beta branch.
Our goal is that existing apps have every reason to upgrade and no reason to stay behind. When this happens, the Ember community moves forward together and benefits from a common platform for improvement and collaboration.
2. Ember 2.10 is Fast
The main focus of Ember 2.10 has always been a successful and backwards compatible integration of Glimmer 2. Despite this, a major motivation for the overall effort was obviously performance. We consider the current system to be stable but un-tuned. There are some positive performance improvements, but also a lot of potential.
“Performance” in JavaScript libraries is a pretty hand-wave matter unfortunately, muddled by races to capture the mantle of “fastest!” based on benchmarks interesting to other library developers but woefully inapplicable to the real world. Real apps must perform well in a large number of scenarios, and balance many overlapping concerns. Some of the concerns the Glimmer contributors have been thinking about and tracking are:
- Byte-size of the Ember framework itself
- Byte-size of real-world Ember applications post-build
- Parse and initial eval time of JavaScript payloads in the browser
- Ember’s initial template render performance
- Ember’s template re-render performance
These facets of a well-performing app have been studied in contrived benchmarking scenarios and real applications. For example developers working on Skylight, Intercom, LinkedIn’s new mobile app and others have tracked positive performance changes to their applications in the wild.
You should track important performance numbers in your own apps, as that is the best way to understand changes relevant to your users. However I’m happy to report that Ember 2.10 should bring a notable improvement to app performance on day one:
Template compilation output in Ember 2.10 is very different than in Ember 2.9. The new format is more easily parsed by JavaScript engines, has fewer allocations on evaluation, and is more terse. LinkedIn saw the uncompressed compiled template size for their mobile app drop by 71%. When experimenting with early betas Intercom saw a 28% reduction of their gzipped application JS payload from 1.33M to 953K.
😍 Glimmer 2 will reduce @intercom’s uncompressed #emberjs app size by over 4MB and gzipped will be reduced by 30% pic.twitter.com/MJR0xuRJxQ
— Gavin Joyce (@gavinjoyce) September 12, 2016Ember 2.10 increased the minified and gzipped size of the framework from 107K to 127K. For most apps, the benefits in template size will outweigh this increase. However we’ve also taken other steps toward using a rollup solution for package-internal modules in framework builds. These improvements should (based on experimentation) eliminate the framework bloat and improve parse/eval time. This work didn’t make it into 2.10 and is somewhat coupled to the JavaScript Modules API RFC, but we look forward to landing it soon.
The Discourse project has long tracked a specific benchmark related to initial render via the ember-performance repo. To upgrade beyond Ember 1.12, they required that benchmark to improve (In Ember 1.13 the benchmark regressed nearly 2x). Previous 2.x releases have made small improvements, but Ember 2.10 finally crosses the threshold: Ember 2.10 is 2x faster on the “Render Complex List” scenario than Ember 2.9, and slightly faster than 1.11. With this news in hand, Discourse has begun the upgrade to Ember 2.10!
The “dbmonster” or “dbmon” demo is a reasonable proxy for template re-render performance. On this dbmon implementation from Chris Freeman Ember 2.10 rerenders 1.5x faster than Ember 2.9.1, improving from 18fps to 28fps. Another dbmon implementation (details, demo) hits 60fps on 2.10 (All fps numbers on Chrome 55, i7 running OSX).
We expect that Ember 2.10 improves the performance of real-world applications across the board. It is exciting to see such meaningful improvements arrive without application code needing to be refactored or rewritten. There remains a lot of potential for improvement as well.
3. Glimmer Started a TypeScript Revolution in Ember
Microsoft’s TypeScript project had some rough early days, but the tooling and syntax has improved at an impressive clip.
The Glimmer codebase is written in TypeScript and has been for about a year. Other micro libraries that support Ember, such as dag-map, have also been ported over. TypeScript has helped Ember contributors reason about and navigate complex codebases. It has helped them minimize polymorphic code that v8 handles poorly.
It wouldn’t be surprising to see TypeScript start to land in the Ember project itself, however I think TypeScript as a popular and productive tool for Ember applications is still a ways off. Ember’s DI and templating systems introduce a lot of potentially dynamic types. Until we adopt ES modules the inability for TypeScript to easily infer types in Ember apps will be a significant limitation to adoption at the application level.
4. Glimmer is a Solid Foundation
Glimmer 2 is a major architectural overhaul from Glimmer 1 (which shipped in Ember 1.13). In Glimmer 2 a precompiler converts template syntax to a wire format that looks suspiciously like opcodes. At runtime, the wire format is converted into actual opcodes for a DOM appending virtual machine. At initial render the appending VM returns opcodes for the updating VM.
For example this template:
<h1>{{title}}</h1>
<div class="posts">
{{#each posts key='id' as |post|}}
<h2 data-id={{post.id}}>{{post.title}}</h2>
{{post.body}}
<hr>
{{/each}}
</div>
Creates a wire format (by this we mean the version shipped over HTTP to browsers) like this:
// Top-Level Template
["open-element","h1",[]]
["flush-element"]
["append",["unknown",["title"]],false]
["close-element"]
["text","\n"]
["open-element","div",[]]
["static-attr","class","posts"]
["flush-element"]
["text","\n"]
["block",["each"],[["get",["posts"]]],[["key"],["id"]],0]
["close-element"]
// Each Block
["text"," "]
["open-element","h2",[]]
["dynamic-attr","data-id",["unknown",["post","id"]],null]
["flush-element"]
["append",["unknown",["post","title"]],false]
["close-element"]
["text","\n "]
["append",["unknown",["post","body"]],false]
["text","\n "]
["open-element","hr",[]]
["flush-element"]
["close-element"]
["text","\n"]
When required, then wire format is parsed into append opcodes:
OPEN-PRIMITIVE-ELEMENT("h1")
FLUSH-ELEMENT
PUT-VALUE(self.title)
OPTIMIZED-CAUTIOUS-APPEND($OPERAND)
CLOSE-ELEMENT
TEXT("\n")
OPEN-PRIMITIVE-ELEMENT("div")
STATIC-ATTR(name="class", value="posts")
FLUSH-ELEMENT
TEXT("\n")
PUT-ARGS(positional=[self.posts], named={key: "id"})
ENTER("BEGIN [4349]", "END [4350]")
LABEL("BEGIN [4349]")
PUT-ITERATOR
JUMP-UNLESS("END [4350]")
ENTER-LIST("BEGIN [4354]", "END [4355]")
LABEL("ITER [4357]")
NEXT-ITER
ENTER-WITH-KEY("BEGIN [4354]", "END [4355]")
LABEL("BEGIN [4354]")
EVALUATE(default)
PUSH-CHILD-SCOPE
BIND-POSITIONAL-ARGS(["post"])
TEXT(" ")
OPEN-PRIMITIVE-ELEMENT("h2")
PUT-VALUE($1(post).id)
DYNAMIC-ATTR(name="data-id", value=$OPERAND)
FLUSH-ELEMENT
GUARDED-CAUTIOUS-APPEND($1(post).title)
CLOSE-ELEMENT
TEXT("\n ")
GUARDED-CAUTIOUS-APPEND($1(post).body)
TEXT("\n ")
OPEN-PRIMITIVE-ELEMENT("hr")
FLUSH-ELEMENT
CLOSE-ELEMENT
TEXT("\n")
POP-SCOPE
LABEL("END [4355]")
EXIT
JUMP("ITER [4357]")
LABEL("BREAK [4358]")
EXIT-LIST
LABEL("END [4350]")
EXIT
CLOSE-ELEMENT
These append opcodes are then run through the append VM. The VM writes to DOM and simultaneously emits a set of opcodes for subsequent rerenders with the updating VM. These updating opcodes are specific to the data that was rendered. For example given this data for the above template:
{
"model": {
"title": "My Blog",
"posts": [
{
"id": "4242",
"title": "Hello",
"body": "Oh my"
}
]
}
}
The updating opcodes could be represented as:
OPTIMIZED-CAUTIOUS-UPDATE(lastValue="My Blog")
TRY(guid=4368, begin=BEGIN [4349], end=END [4350])
ASSERT(expected=true)
LIST-BLOCK(guid=4370, begin=BEGIN [4354], end=END [4355], map={"4242": 4371})
TRY(guid=4371, begin=BEGIN [4354], end=END [4355])
PATCH-ELEMENT(element="<h2 />", type=attribute, namespace=undefined, name=data-id, lastValue=4242)
GUARDED-CAUTIOUS-UPDATE(lastValue="Hello")
GUARDED-CAUTIOUS-UPDATE(lastValue="Oh my")
This program is optimally compiled for the template and data being rendered.
Note that it tracks no information about the static <div class="posts">
element.
The more Glimmer can learn about your program (for example which values are
constants, or which values are passed to another template to be re-used) the
further it can optimize the update path.
Thinking of Glimmer as a programming language with VMs for various scenarios has been extremely powerful. For example, consider “rehydration”, a term we’ve used to describe the re-use of existing DOM when an Ember app boots in the browser: Today, Ember Fastboot can render an application on the server. Despite having the DOM already hydrated with data, once Ember boots on the client it replaces all that DOM with newly created DOM from the append VM. Let’s brainstorm how rehydration might be implemented: On the server, an initial render creates HTML and simultaniously emits updating opcodes which are today unused. Those update opcodes (based on the template and data), if serialized and passed to the client, can be used for a fast DOM-reusing client-side rerender with the updating VM. In practice a rehydration VM will likely run instead of the update VM to manage special cases in HTML serialization of DOM.
Earlier versions of the Ember rendering engine could often feel like so many domain-specific abstractions piled on top of each other. The unifying abstraction of a virtual machine has been liberating.
If you’re interested in learning more about Glimmer, check out this 75 minute deep-dive with Yehuda Katz and Gavin Joyce.
5. Shipping Means There is More to Do
Integrating Glimmer into Ember with full backwards compatibility has taken significant effort, learning, and skill. The community of contributors and businesses that worked to ship this improvement didn’t do so just because they love a challenge. They have users that demand improvements both evolutionary and revolutionary. Stabilization of the framework’s new rendering engine means thousands of apps serving millions of end users have an path toward better experiences. Additionally, hundreds of contributors have a great foundation on top of which to build exciting new Ember features.
One lesson learned from the integration effort is how well “quest issues” can empower contributors. When Glimmer 1 was integrated into Ember 1.13 there were a large number of regressions. This was despite Ember’s test suite having been completely ported to cover the new engine. This made it obvious that the test suite in Ember was far from adequate. To have a small number of core developers port and expand the test coverage would have taken a fearfully long time. Instead of relying on a test suite known to be poor, Godfrey Chan wrote issue #13127 not knowing if the time spent describing what needed to be done would be wasted.
However his detailed call to action was incredibly effective. Contributors new and old completed a rewrite of the test suite that expanded coverage, improved readability, and for some provided their first PR to Ember. Well written quest issues are a powerful and important tool for any open source project.
In the next several months you’ll see increased discussion of new features on the RFC repo and Github issues for Ember, as well as further performance tuning of the framework. If you’re interested in contributing to Ember or learning more about the framework, check out emberjs.com.
To the many, many people who worked on Glimmer2 over the past year and who made this work so easy for others to use: Thank you!
Additional thanks to Mitch Lloyd, Cory Forsyth, Isaac Ezer, Ricardo Mendes, and Godfrey Chan for their feedback on this post.