Ember and Babel, to ES7 and beyond

A clear vision of the future

By Tom Coquereau / @thaume / Blaaast

Summary

How is Ember moving to the new JavaScript ? (and features you should be aware of)

  1. What is Babel ?
  2. ES6/ES7 important features
  3. A practical Ember example

What is Babel ?

An ECMAScript transpiler

2009: TC-39 decides to create an ever-evolving version of ECMAScript

Developers crave to use the new features (unavailable in most browsers)

Babel transpiles the code so that any browser can run the modern code you type

Old and new name

6to5 => Babel

ES6, ES7, ES2015, ES2016...

Babel is future-proof

You will always need Babel to use new language features

ES6/ES7 important features

The arrow function


// ES3 way of binding scope
(function() {
  var _this = this;
  this.age = 24;

  someAsyncFunc().then(function() {
    console.log(this.age); // undefined
    console.log(_this.age); // prints 24
  });
}());

// ES5 way of binding scope
(function() {
  this.age = 24;
  someAsyncFunc().then(function() {
    console.log(this.age); // prints 24
  }.bind(this));
}());

// ES6 way of binding scope
(function() {
  this.age = 24;
  someAsyncFunc().then(() => {
    console.log(this.age); // prints 24
  });
}());

// Fancy ES6 map
const ids = rawData.map(data => data.id);

The module system


// file path: ./models/user.js
export class User { ... };

// file path: ./config/user.js
export let age = 24;
export const name = 'Tom';

// file path: ./utils.js
export default function toArray() { ... }

// file path: ./index.js
import { User } from './models/user';
import { age, name } from './config/user';
import toArray from './utils';

Destructuring for objects


var user = {
  age: 24,
  name: 'Tom',
  company: 'Blaaast'
};

// ES5 way of getting an object's properties
var age = user.age;
var name = user.name;

// ES6 way of getting an object's properties
const { age, name } = user;

Destructuring for arrays


var usersAges = [24, 32, 54, 16, 42];

// ES5 way of getting an array's elements
var userOneAge = usersAges[0];
var userFourAge = usersAges[3];

// ES6 way of getting an array's elements
const [userOneAge, , ,userFourAge] = usersAges;

Class


// ES6 classes
class User {
  constructor() {
    this.age = 24;
    this.name = 'Tom';
  }
  heresMyName() {
    return this.name;
  }
  get whatsMyName() {
    return this.name;
  }
  set whatsMyName(newName) {
    this.name = newName;
  }
  static hereIsAName() {

  }
  name
}

// extending classes in ES6
class Player extends User {
  constructor() {
    this.team = 'Yankee'; // throw error
    super(...arguments);
    this.team = 'Yankee'; // 'this' is available after 'super' call on extended classes
  }
  heresMyName() {
    super(...arguments);
    // Additional code specific for the player class
  }
}

Decorators


class User {
  @readOnly
  fullName() { return `${this.first} ${this.last}` }
}

// Installing the property
Object.defineProperty(Person.prototype, 'name', {
  value: specifiedFunction,
  enumerable: false,
  configurable: true,
  writable: true
});

// How it roughly works
function readOnly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

let descriptor = readOnly(User.prototype, 'jobsByType', {
  enumerable: false,
  writable: true,
  configurable: false,
  value: null
});
Object.defineProperty(User.prototype, 'jobsByType', descriptor);

// Can take params and be applied to a class
@isTestable(true)
class MyClass { ... }

Async functions


async function() {
  this.set('isLoading', true);

  try {
    return await save();
  } catch {
    // Called on error
  } finally {
    // Always called once everything is done
    this.set('isLoading', false);
  }
}

Going beyond

for..of, map, weakmap, set, weakset, generator (yield), proxies...

A practical Ember example Ember jobs

Destructuring


const { inject, computed } = Ember;
// Is equivalent to
const inject = Ember.inject;
const computed = Ember.computed;

// What does that mean ?
import computed, { readOnly } from 'ember-computed-decorators';
// Computed => default export
// Readonly => named export from 'ember-computed-decorators'

New objects literal notation


// file path: ./app/controllers/index.js
import Ember from 'ember';

export default Ember.Controller.extend({
  ...

  actions: {
    jobTypeChanged(type) {
      this.set('type', type);
    },

    searchChanged(search) {
      this.set('search', search);
    }
  }
});

Async functions


// file path: ./app/pods/new-position-form/component.js
export default Ember.Component.extend({
  ...

  actions: {

    async save() {
      try {
        let company = await company.save();

        return await store.createRecord('job', jobAttributes).save();
      }
    }

  }
}

Testing (before Babel)


// file path: ./tests/acceptance/new-job-test.js
test('visiting / and opening/closing the  new job modal', function test(assert) {
  server.get('/jobs',      json(200, { jobs:      [] }));
  server.get('/companies', json(200, { companies: [] }));

  visit('/');

  click('#post-job');

  andThen(function() {
    isVisible(assert, '.new-job-modal');
  });

  click('.our-modal');

  andThen(function() {
    isNotVisible(assert, '.new-job-modal');
  })
});

Testing (after Babel)


// file path: ./tests/acceptance/new-job-test.js
test('visiting / and opening/closing the  new job modal', async function test(assert) {
  server.get('/jobs',      json(200, { jobs:      [] }));
  server.get('/companies', json(200, { companies: [] }));

  await visit('/');
  await click('#post-job');

  isVisible(assert, '.new-job-modal');

  await click('.our-modal');

  isNotVisible(assert, '.new-job-modal');
});

Using ES7 decorators


filteredJobs: Ember.computed('jobsByType.[]', function() { ... });

@computed('jobsByType.[]')
filteredJobs(jobs) {
  ...
}

@computed('type', 'search', 'model.@each.{title,description,type,location,company,name}')
@readOnly
jobsByType(type, search) {
  ...
}

firstName: Ember.computed.alias('first')

@alias('first') firstName,
@empty('first') missingFirstName,
@match('first', /rob/i) isRob

// Ember data models
export default DS.Model.extend({
  @attr({ defaultTo: 'smith' }) name,
  @attr logo,
  @attr url,
  @attr jobs,

  @hasMany accounts,
  @belongsTo human

});

Conclusion

How is Ember moving to the new JavaScript ? (and features you should be aware of)

Transpilation ES6/7 => ES5 (and ES3 on activation)

Addons for future ES7 not out-of-the-transpiler implementations

Questions ?