Optimizing Ember Templates

Optimizing Ember Templates

Templates make up 60% of your Ember app. Now what?

Update: Ember’s base performance has improved a lot since this article was first published! Ember 2.10 shipped with a new rendering engine that makes some of these optimizations unnecessary. If you’re working on an older app, 2.9 or less, keep reading!

When source code goes through a compiler or transpiler, it’s easy to create a disconnect between the perceived weight of a piece of code and its actual size in the shipping product.

This is especially true with Ember templates, where small changes can have a profound impact on the amount of JavaScript that is generated.

“Every byte matters.”

— Every Front-end Web Developer

(after exhausting the other ways of optimizing app startup)

Conditional Blocks are Expensive

Consider a component template that renders two paragraphs, the second is conditionally rendered using an #if helper:

<p class=”instructions1">{{instructionsOne}}</p>
{{#if instructionsTwo}}
  <p class=”instructions2">{{instructionsTwo}}</p>

The total size of the generated code for this component is 3.8 KiB, surprisingly large! Where does all the code come from?

Each template is converted into a JavaScript module at build time. Each module contains the logic, code and data needed to create the DOM and populate its children and values (sample below).

The surprise here is in the way the conditional IF block is compiled. It turns into a full template child, almost as large as the parent template, complete with metadata and rendering functions of its own (shown in red).

Generated JavaScript for Template with Conditional IFGenerated JavaScript for Template with Conditional IF

The {{#if}} helper is very easy to use, and mentally it’s a lightweight solution to a conditional situation. However, behind the scenes each conditional block is actually represented by the same infrastructure as an entire new template.

If instead we conditionally hide the paragraph element using a class name it removes half the generated code and saves 1.2 KiB off the total app size!

<p class=”instructions1">{{instructionsOne}}</p>
<p class=”instructions2 {{unless instructionsTwo ‘hide’}}”>{{instructionsTwo}}</p>

This may seem like a controversial way to optimize a template, but it illustrates the general concept that we will continue to explore: small changes can save a lot of code, sometimes at the expense of maintainability and readability.

Using a dynamic class removes an entire template childUsing a dynamic class removes an entire template child

Avoid Repetitive IF/Else using Loops

A more complex template involving a series of If/Else statements shows how generated code can become even larger. Every conditional block is converted into a child template the same size as the red text in the example above. So while this looks like one template, it’s actually eight.

<section class=’instruments-container’>
  {{#if model.storageInstrument}}
    {{settings-instrument-item instrument=model.storageInstrument action=”manageBalance”}}
  {{#if combinedBankInstrument}}
    {{settings-instrument-item instrument=combinedBankInstrument action=”manageBankAccount”}}
  {{else if model.debitInstrument}}
    {{settings-instrument-item instrument=model.debitInstrument action=”removeOrReplaceInstrument”}}
  {{else if model.bankInstrument}}
    {{settings-instrument-item instrument=model.bankInstrument action=”removeOrReplaceInstrument”}}
    {{settings-instrument-item type=”bankAccount” action=”addDebitCard”}}
  {{#if model.creditInstrument}}
    {{settings-instrument-item instrument=model.creditInstrument action=”removeOrReplaceInstrument”}}
    {{settings-instrument-item type=”creditCard” action=”addCreditCard”}}

We can refactor this component using a #each loop and move the creation of the instrumentList array into javascript:

<section class=’instruments-container’>
  {{#each instrumentList as |listItem|}}
          showAlertIcon=listItem.showAlertIcon }}

Even when accounting for the additional JavaScript needed to build the instrumentList, the new implementation saves 12 KiB of code.

Computed Properties

Another technique for avoiding conditional blocks in templates is to move the work into computed properties. In the example below you save more than 50% of the component size by calculating the displayed value in JavaScript.

SVG In Templates

SVG also increases in size when used in templates. This SVG file renders a checkmark inside a circle; the source is 298 bytes but compiles to 2.2 KiB of JavaScript!

<svg viewBox=”0 0 300 300">
    <path class=”circle animatable reverse” d=’M 150, 150 m -0, -150 a -150,-150 0 1,0 0,300 a -150,-150 0 1,0 0,-300' fill=”none” stroke-width=”19" />
    <path class=”check animatable” fill=”none” d=’M 90,160 l 45,45 l 90,-95' stroke-width=”19" />
  • Remember to run the SVG through a minifier like SVGO before adding it to your template.

  • Manually inspect the result to cleanup any additional unused attributes.

  • You can safely omit the xmlns and version attributes on inline SVG.

Better yet, try moving the SVG into an external file and referencing it as a background image or as the source for an <img> tag. It will save the cost of the entire template, a wise choice if it’s rarely used. Sometimes you can even create the same image using CSS!

Use the Component’s Implicit Tag

Every Ember component has an implicit wrapping tag that can be used for styling and layout. Even though it’s not defined in the template, you can customize that tag using the component’s JavaScript file.

In the Loop example above we can optimize out the wrapping tag <section class=’instruments-container’> by modifying our component:

export default Ember.Component.extend({
  tagName: 'section',
  classNames: ['instruments-container'],
  layoutName: 'components/instruments-container',

Any template with a single top level element in the template is a candidate for using the implicit component tag instead. This technique also helps reduce the number of nodes in the DOM.

Extend Components instead of Wrapping

When adding new functionality to an existing component it can be tempting to wrap the base component in a new template and relay all the attributes. This has the unfortunate side effect of creating a new template file, and can often be avoided entirely by extending the existing component:

import ValidatedInput from 'components/validated-input';

export default ValidatedInput.extend({
  placeholder: i18n.t('placeholders.email'),
  type: 'email'

Going Further

Understanding the Ember template compiler helps make these techniques more intuitive. A quick way to go deeper is to implement the same template in multiple ways and compare the results. You can use a diff tool to see how the final output compares and which method works best for your scenario. Happy templating!

Table Of Contents