Hey there! I'm Anh, Dollar Shave Club's first official Front End Engineering Intern. I've worked on many cool projects this summer, from migrating outdated web pages to Ember, to deploying an A/B test.

In this post, I wanted to share a more in depth look on how I updated our front end framework to Ember's latest release.

Note: This article took longer than expected to be revised and published. Some information might be outdated.


The Journey from v2.4.3 to v2.8.0

It started off as: bower install --save ember#2.8.0-beta.5
...and everything broke.

So then I backtracked to: bower install --save ember#2.7.3
...and everything was still broken.

Then on September 8, 2016, Ember officially releases 2.8 and 2.9 Beta.
So then I tried: bower install --save ember#2.9.0-beta.1
...and less things were broken!

I worked on v2.9.0.beta-1 for a little while, and actually got our site to load, but it was decided that getting v2.8.0 to work would be in the team's best interests. Hopefully I'll get a chance to revisit the beta version again so I can see how the Glimmer 2 engine will affect the rendering performance of our application.

Back Track to Ember 2.8.0

bower install --save ember#2.8.0

Bug Fixes

After installing the new version of Ember, it was time to tackle any issues that arose.

  • Error - Cannot set attributeBindings on a tagless component

In one of our initializers, we were reopening the Ember.Component class, and adding an additional attribute to attributeBindings.

// THE ISSUE
Ember.Component.reopen({
 attributeBindings: ['testId:data-test-id'],
});

Since not all of our components contained a tag associated with it, the above error was being thrown.

// THE SOLUTION
Ember.Component.reopenClass({
 attributeBindings: ['testId:data-test-id'],
});

Instead of using reopen, I changed the initializer to use reopenClass.

  • Error - Assertion Failed: Cannot call get with 'slugRoutableModelName' on an undefined object.
// THE ISSUE
serialize(model) {
  if (!model) return;
  const out = {};
  const modelName = get(this, 'slugRoutableModelName') || this.routeName.split('.').pop();
  out[modelName + 'Slug'] = get(model, 'slug');
  return out;
},

In the serialize function of this particular mixin, this is referring to the current route that contains the mixin. Somewhere along the way, this became undefined.

// THE SOLUTION
serialize(model, params) {
  if (!model) return null;
  const out = {};
  out[params] = get(model, 'slug');
  return out;
},

It turns out our logic to define modelName was unnecessary since we could obtain the model slug from the route params.

  • Error - Must use Ember.set() to set the parentView of <component:...> to <component:...>

The issue was in our ember-uni-form addon.

// THE ISSUE
export default Ember.Mixin.create({
  parentFormView: Ember.computed('parentView', function () {
    var parentView = this;
    while (parentView = parentView.get('parentView')) {
      if (parentView.get('tagName') === 'form') return parentView;
    }
  }),
});

The parentFormView property was a computed property which observed parentView. After perusing Ember's issue tracking on Github, I came across this and this. Basically, Ember doesn't support observing the parentView property.

// THE SOLUTION
function getFormView(parentView) {
  if (!parentView) return null;
  if (parentView.get('tagName') === 'form') return parentView;
  return getFormView(parentView.get('parentView'));
}

export default Ember.Mixin.create({
  parentFormView: Ember.computed(function () {
    return getFormView(this.get('parentView'));
  }).volatile(),
});

By making the computed function volatile, it no longer observes the parentView property. We also extracted the while loop and created a recursive getFormView function, which cleans up our logic a bit.

  • Visual Bug - Images sometimes not showing

    • Error: Cannot read property ‘indexOf’ of undefined
    • Error: Cannot read property ‘length’ of undefined

Updating jQuery to v3.1.0 deprecates the load() function.

// THE ISSUE
$img.load();

Further explanation is available here. The event .load() method conflicted with the ajax .load() method.

// THE SOLUTION
$img.trigger('load');

Instead of using load(), we now kick off the load event by using .trigger().


Failing Tests

While I was on Ember v2.9.0.beta-1, various component unit tests were raising the following error: TypeError: Template.create is not a function. They were simple tests, checking to see whether or not the component was rendering, but these same tests were passing on v2.8.0. I believe this might be due to compatibility issues with Glimmer 2 and Ember.HTMLBars.compile. I'll definitely be taking a closer look into this problem if/when we test out our app with Ember's beta version!


Deprecations

  • Enumerable#contains is deprecated, use Enumerable#includes instead.

My method of attack was find-all-and-replace. If you're doing this, be careful, since there might be cases where contains exists for a specific enumerable type, but includes does not. In my case, it was Element.classList, which returns a DOMTokenList. As you can see from the API docs, DOMTokenList does not have an includes method.

  • Ember.Handlebars.SafeString is deprecated in favor of Ember.String.htmlSafe

Find-all-and-replace worked well for this one. There's also a handy .isHTMLSafe() function to replace using instanceof Ember.Handlebars.SafeString.

  • When calling Ember.warn you must provide id in options.

This was also a quick fix. All I had to do was pass an additional object argument with an id property set.


Ember Addon Updates

  • ember-cli-head: Updated to latest version, which is compatible with v2.9 beta
  • ember-metrics: Updated our fork with deprecation fixes from project owner
  • ember-cli-flash: Need to update our package to latest version (1.3.17)

Notes

  • Time it took

I started this project on September 7th, and it took about four days to finalize updating any code or addons that were giving errors.

September 23rd: My internship has ended, and the Ember upgrade still hasn't been deployed. :disappointed:

But that's alright because jump to September 26th: WE ARE LIVE. Thanks to my buddy, Arjan, for merging my PR. :tada:

  • Particularly challenging areas

    • Navigating through various Ember addons, and figuring out which ones needed to be updated.
    • Updating said addons
  • Lessons learned and project takeaways

Some of the most helpful tools I used during my debugging process were the Chrome Developer Tools and the Ember Inspector. I also couldn't have done this without the help of various coworkers that took the time to sit down with me to work through any particularly difficult problems.

This project also helped further my understanding of Ember and how our codebase interacted with different Ember addons.

Thanks for reading!