EmberConf 2019 Keynote

9bf3a766e037b9d5a4da0a6f9d0f4f68?s=47 tomdale
March 19, 2019

EmberConf 2019 Keynote

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

9bf3a766e037b9d5a4da0a6f9d0f4f68?s=128

tomdale

March 19, 2019
Tweet

Transcript

  1. None
  2. Code of Conduct

  3. None
  4. A framework for creating ambitious web applications.

  5. Climb the Mountain Together

  6. Climb the Mountain Together

  7. The Celery Test

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

    • Code Mods • Templates • Engage with Standards • Invest in Community • Addons What We Did
  9. • <script> tag • Web Components • Framework reboot •

    Ember Native What We Didn’t Do
  10. Progress Stability

  11. Aggressive Changes Cautious Changes Aggressive Changes

  12. Falling Behind Community Fragmenting Aggressive Changes Cautious Changes Aggressive Changes

  13. 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
  14. Falling Behind Community Fragmenting Em ber 1.13 Em ber 2.0

  15. Falling Behind Community Fragmenting Em ber 2.0

  16. Ember 3.0

  17. Co

  18. Co

  19. this.get('firstName') this.set('lastName') this.firstName this.set('firstName') Co

  20. {{user-avatar user=this.currentUser}} <UserAvatar @user={{this.currentUser}} /> Co

  21. {{input value="hello"}} <Input @value="hello" /> Co

  22. Pit of Incoherence Incremental Change Where We Are Where We

    Want to Be
  23. Pit of Incoherence Incremental Change Where We Are Where We

    Want to Be
  24. Where We Are Where We Want to Be

  25. Where We Are Where We Want to Be

  26. Ready for Everyone Where We Are Where We Want to

    Be
  27. None
  28. OCTANE

  29. OCTANE

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

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

    addons updated to not need jQuery No jQuery Required
  32. No jQuery Required

  33. • Standard JavaScript syntax • Knowledge portability • Integration with

    IDEs, linters • Works well with TypeScript JavaScript Classes
  34. 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
  35. 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; } } }
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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; } } }
  41. 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
  42. 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
  43. Angle Bracket Invocation {{weather-tracker zip="10001"}} <WeatherTracker @zip="10001" />

  44. Angle Bracket Invocation <WeatherTracker @zip="10001" aria-label="Weather" />

  45. • Easier to scan visually • One-word components • <User

    /> • HTML attributes Angle Bracket Invocation
  46. • Minimal base class • “Outer HTML” templates • Explicit,

    immutable arguments Glimmer Components
  47. 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
  48. <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
  49. • State of the art change tracking • Fast, efficient

    updates • “Just JavaScript” • No need for this.set() Tracked Properties
  50. 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++; } }
  51. 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}`; } }
  52. 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); } }
  53. 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(); }
  54. None
  55. None
  56. One Simple Rule If you change a class field and

    want the DOM to update, make it tracked.
  57. • No jQuery Required • JavaScript Classes • Glimmer Components

    • Tracked Properties
  58. None
  59. <div class="weather"> <h2>Current weather</h2> <address> New York, NY </address> <div

    class="temperature"> 55° </div> </div> <WeatherTracker /> weather-tracker.hbs
  60. 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
  61. 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
  62. 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
  63. <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
  64. 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
  65. 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
  66. <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
  67. None
  68. FUN

  69. P R E V I E W Today

  70. • 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
  71. None
  72. Classic Octane ?

  73. Pipeline • New file layout • Template imports • Embroider

  74. Embroider • Faster builds • Asset optimization • Built on

    JavaScript modules
  75. 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
  76. HBS ES5 CSS Packager • Optimize and compile for the

    browser APP.JS APP.CSS
  77. None
  78. None
  79. HBS ES5 CSS Packager • Optimize and compile for the

    browser APP.JS APP.CSS webpack Rollup
  80. 2019 Roadmap

  81. Climb the Mountain Together

  82. None
  83. 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
  84. 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
  85. Thank you!