Upgrade to Pro — share decks privately, control downloads, hide ads and more …

EmberConf 2019 Keynote

tomdale
March 19, 2019

EmberConf 2019 Keynote

The opening keynote from EmberConf 2019, hosted in Portland, OR. Presented by Tom Dale and Yehuda Katz.

tomdale

March 19, 2019
Tweet

More Decks by tomdale

Other Decks in Programming

Transcript

  1. • Ember CLI • Six-Week Release Cycle • RFC Process

    • Code Mods • Templates • Engage with Standards • Invest in Community • Addons What We Did
  2. Falling Behind Community Fragmenting Em ber 1.13 Improvements • New

    rendering engine • Eliminate metamorph tags • Closure actions • Helper API • hasBlock Deprecations • Ember.View • {{view}} helper • Ember.Select • itemController • bind-attr
  3. Co

  4. Co

  5. • No jQuery Required • JavaScript Classes • Angle Bracket

    Invocation • Glimmer Components • Tracked Properties Ember Octane A better component system
  6. • Off by default for new apps • Documentation, tests,

    addons updated to not need jQuery No jQuery Required
  7. • Standard JavaScript syntax • Knowledge portability • Integration with

    IDEs, linters • Works well with TypeScript JavaScript Classes
  8. Classic Class import { computed } from '@ember/object'; import {

    inject as service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default AjaxService.extend({ session: service(), namespace: '/api/v2', headers: computed('session.{isAuthenticated,header}', function() { if (this.get('session.isAuthenticated')) { return this.get('session.header'); } }) }); Source: Ember Observer
  9. JavaScript Class import { computed } from '@ember/object'; import {

    inject as service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default class extends AjaxService { @service session; namespace = '/api/v2'; @computed('session.{isAuthenticated,header}') get headers() { if (this.session.isAuthenticated) { return this.session.header; } } }
  10. import { computed } from '@ember/object'; import { inject as

    service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default class extends AjaxService { @service session; namespace = '/api/v2'; @computed('session.{isAuthenticated,header}') get headers() { if (this.session.isAuthenticated) { return this.session.header; } } } import { computed } from '@ember/object'; import { inject as service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default AjaxService.extend({ session: service(), namespace: '/api/v2', headers: computed('session.{isAuthenticated,header}', function() { if (this.get('session.isAuthenticated')) { return this.get('session.header'); } }) }); Classic Class JavaScript Class
  11. import { computed } from '@ember/object'; import { inject as

    service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default class extends AjaxService { @service session; namespace = '/api/v2'; @computed('session.{isAuthenticated,header}') get headers() { if (this.session.isAuthenticated) { return this.session.header; } } } import { computed } from '@ember/object'; import { inject as service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default AjaxService.extend({ session: service(), namespace: '/api/v2', headers: computed('session.{isAuthenticated,header}', function() { if (this.get('session.isAuthenticated')) { return this.get('session.header'); } }) }); Classic Class JavaScript Class
  12. import { computed } from '@ember/object'; import { inject as

    service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default class extends AjaxService { @service session; namespace = '/api/v2'; @computed('session.{isAuthenticated,header}') get headers() { if (this.session.isAuthenticated) { return this.session.header; } } } import { computed } from '@ember/object'; import { inject as service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default AjaxService.extend({ session: service(), namespace: '/api/v2', headers: computed('session.{isAuthenticated,header}', function() { if (this.get('session.isAuthenticated')) { return this.get('session.header'); } }) }); Classic Class JavaScript Class
  13. import { computed } from '@ember/object'; import { inject as

    service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default class extends AjaxService { @service session; namespace = '/api/v2'; @computed('session.{isAuthenticated,header}') get headers() { if (this.session.isAuthenticated) { return this.session.header; } } } import { computed } from '@ember/object'; import { inject as service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default AjaxService.extend({ session: service(), namespace: '/api/v2', headers: computed('session.{isAuthenticated,header}', function() { if (this.get('session.isAuthenticated')) { return this.get('session.header'); } }) }); Classic Class JavaScript Class
  14. import { computed } from '@ember/object'; import { inject as

    service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default AjaxService.extend({ session: service(), namespace: '/api/v2', headers: computed('session.{isAuthenticated,header}', function() { if (this.get('session.isAuthenticated')) { return this.get('session.header'); } }) }); Classic Class JavaScript Class import { computed } from '@ember/object'; import { inject as service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default class extends AjaxService { @service session; namespace = '/api/v2'; @computed('session.{isAuthenticated,header}') get headers() { if (this.session.isAuthenticated) { return this.session.header; } } }
  15. import { computed } from '@ember/object'; import { inject as

    service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default class extends AjaxService { @service session; namespace = '/api/v2'; @computed('session.{isAuthenticated,header}') get headers() { if (this.session.isAuthenticated) { return this.session.header; } } } import { computed } from '@ember/object'; import { inject as service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default AjaxService.extend({ session: service(), namespace: '/api/v2', headers: computed('session.{isAuthenticated,header}', function() { if (this.get('session.isAuthenticated')) { return this.get('session.header'); } }) }); Classic Class JavaScript Class
  16. import { computed } from '@ember/object'; import { inject as

    service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default class extends AjaxService { @service session; namespace = '/api/v2'; @computed('session.{isAuthenticated,header}') get headers() { if (this.session.isAuthenticated) { return this.session.header; } } } import { computed } from '@ember/object'; import { inject as service } from '@ember/service'; import AjaxService from 'ember-ajax/services/ajax'; export default AjaxService.extend({ session: service(), namespace: '/api/v2', headers: computed('session.{isAuthenticated,header}', function() { if (this.get('session.isAuthenticated')) { return this.get('session.header'); } }) }); Classic Class JavaScript Class
  17. • Easier to scan visually • One-word components • <User

    /> • HTML attributes Angle Bracket Invocation
  18. Classic Component import Component from "@ember/component"; export default Component.extend({ tagName:

    'button', elementId: 'my-button', classNames: ['btn'], classNameBindings: ['isDangerous:dangerous'], attributeBindings: ['isDisabled:disabled'], isDangerous: false, isDisabled: false, }); my-button.js my-button.hbs Click Me
  19. <button id="my-button" class="btn {{if @isDangerous 'dangerous'}}" disabled={{@isDisabled}}> Click Me </button>

    my-button.js my-button.hbs Click Me import Component from "@ember/component"; export default Component.extend({ tagName: 'button', elementId: 'my-button', classNames: ['btn'], classNameBindings: ['isDangerous:dangerous'], attributeBindings: ['isDisabled:disabled'], isDangerous: false, isDisabled: false, }); Glimmer Component my-button.hbs Classic Component
  20. • State of the art change tracking • Fast, efficient

    updates • “Just JavaScript” • No need for this.set() Tracked Properties
  21. Tracked Properties import Component from "@glimmer/component"; import { tracked }

    from "@glimmer/tracking"; import { action } from "@ember/object"; export default class extends Component { @tracked count = 0; @action increment() { this.count++; } }
  22. Computed Tracked Properties import Component from "@glimmer/component"; import { tracked

    } from "@glimmer/tracking"; export default class extends Component { @tracked firstName = "Godfrey"; @tracked lastName = "Chan"; get fullName() { return `${this.firstName} ${this.lastName}`; } }
  23. Tracked Properties, Anywhere import { tracked } from "@glimmer/tracking"; export

    default class Person { @tracked firstName; @tracked lastName; @tracked birthYear; constructor(firstName, lastName, birthYear) { this.firstName = firstName; this.lastName = lastName; this.birthYear = birthYear; } get age() { return (new Date()).getFullYear() - this.birthYear; } randomizeAge() { this.birthYear = 1900 + Math.round(Math.random() * 100); } }
  24. person = new Person("Godfrey", "Chan", 1988); 1. Create a new

    model instance 2. Pass it to a component <UserEditor @person={{this.person}} /> 3. Mutate the model from the component @action randomize() { this.args.person.randomizeAge(); }
  25. One Simple Rule If you change a class field and

    want the DOM to update, make it tracked.
  26. <div class="weather"> <h2>Current weather</h2> <address> New York, NY </address> <div

    class="temperature"> 55° </div> </div> <WeatherTracker /> weather-tracker.hbs
  27. import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking';

    export default class extends Component { @tracked temperature = 55; @tracked city = "New York, NY"; } <div class="weather"> <h2>Current weather</h2> <address> {{this.city}} </address> <div class="temperature"> {{this.temperature}}° </div> </div> <WeatherTracker /> weather-tracker.hbs weather-tracker.js
  28. import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking';

    export default class extends Component { @tracked temperature; @tracked city; async loadWeather() { const response = await fetch(`/api?zip=${this.args.zip}`); const { temperature, city } = await response.json(); this.temperature = temperature; this.city = city; } } <div class="weather"> <h2>Current weather</h2> <address> {{this.city}} </address> <div class="temperature"> {{this.temperature}}° </div> </div> <WeatherTracker @zip="10001" /> weather-tracker.hbs weather-tracker.js
  29. import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking';

    export default class extends Component { @tracked temperature; @tracked city; async loadWeather() { const response = await fetch(`/api?zip=10001`); const { temperature, city } = await response.json(); this.temperature = temperature; this.city = city; } } <div class="weather"> <h2>Current weather</h2> <address> {{this.city}} </address> <div class="temperature"> {{this.temperature}}° </div> </div> <WeatherTracker /> weather-tracker.hbs weather-tracker.js
  30. <div class="weather"> <h2>Current weather</h2> <address> {{this.city}} </address> <div class="temperature"> {{this.temperature}}°

    </div> </div> <WeatherTracker @zip="10001" /> import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; export default class extends Component { @tracked temperature; @tracked city; async loadWeather() { const response = await fetch(`/api?zip=10001`); const { temperature, city } = await response.json(); this.temperature = temperature; this.city = city; } } weather-tracker.hbs weather-tracker.js
  31. import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking';

    export default class extends Component { @tracked temperature; @tracked city; async loadWeather() { const response = await fetch(`/api?zip=${this.args.zip}`); const { temperature, city } = await response.json(); this.temperature = temperature; this.city = city; } } <div class="weather"> <h2>Current weather</h2> <address> {{this.city}} </address> <div class="temperature"> {{this.temperature}}° </div> </div> <WeatherTracker @zip="10001" /> weather-tracker.hbs weather-tracker.js
  32. import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking';

    export default class extends Component { @tracked temperature; @tracked city; constructor() { super(...arguments); this.loadWeather(); } async loadWeather() { const response = await fetch(`/api?zip=${this.args.zip}`); const { temperature, city } = await response.json(); this.temperature = temperature; this.city = city; } } <div class="weather"> <h2>Current weather</h2> <address> {{this.city}} </address> <div class="temperature"> {{this.temperature}}° </div> </div> <WeatherTracker @zip="10001" /> weather-tracker.hbs weather-tracker.js
  33. <div class="weather"> <h2>Current weather</h2> <address> New York, NY </address> <div

    class="temperature"> 55° </div> </div> HTML <div class="weather"> <h2>Current weather</h2> <address> {{this.city}} </address> <div class="temperature"> {{this.temperature}}° </div> </div> Template DOM Output
  34. FUN

  35. • Features available in stable, beta or canary • Try

    it out and give feedback • Review guides, update addons, etc. P R E V I E W emberjs.com/editions/octane
  36. HBS ES5 CSS Source Files “Lingua Franca” HBS SCSS •

    TypeScript, Flow, experimental JS… • Sass, Less, CSS Blocks… • Handlebars, Emblem… • Latest ECMAScript (e.g. ES2018) • CSS • Handlebars
  37. HBS ES5 CSS Packager • Optimize and compile for the

    browser APP.JS APP.CSS webpack Rollup
  38. Jen Weber Framework Chris Garrett Framework Robert Jackson Data Chris

    Thoburn Data Chris Manson Learning Amy Lam Learning Kenneth Larsen Learning Martin Muñoz Alumni Brendan McLoughlin Alumni Clemens Müller Alumni
  39. 2018 Template-only components Brand guidelines Discord Optional jQuery JS classes

    in Ember Data Internal survey Broccoli 2 New Ember CLI docs Ember Times 51x Element modifiers emberjs.com is an Ember app Decorators ember-template-lint Tracked properties Documentary RecordData ember-auto-import Testing improvements <AngleBrackets /> Async leak detection New guides TOC Editions JS classes Rehydration Async testing Reader questions Public infra redux Babel parallel compilation Roadmap RFC