Share
Embed
EmberConf 2019 Keynote
The opening keynote from EmberConf 2019, hosted in Portland, OR. Presented by Tom Dale and Yehuda Katz.
tomdale
March 19, 2019
Transcript
85 slides
-
• Ember CLI • Six-Week Release Cycle • RFC Process
• Code Mods • Templates • Engage with Standards • Invest in Community • Addons What We Did -
• <script> tag • Web Components • Framework reboot •
Ember Native What We Didn’t Do -
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 -
• No jQuery Required • JavaScript Classes • Angle Bracket
Invocation • Glimmer Components • Tracked Properties Ember Octane A better component system -
• Off by default for new apps • Documentation, tests,
addons updated to not need jQuery No jQuery Required -
• Standard JavaScript syntax • Knowledge portability • Integration with
IDEs, linters • Works well with TypeScript JavaScript Classes -
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 -
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; } } } -
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 -
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 -
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 -
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 -
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; } } } -
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 -
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 -
• Easier to scan visually • One-word components • <User
/> • HTML attributes Angle Bracket Invocation -
• Minimal base class • “Outer HTML” templates • Explicit,
immutable arguments Glimmer Components -
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 -
<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 -
• State of the art change tracking • Fast, efficient
updates • “Just JavaScript” • No need for this.set() Tracked Properties -
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++; } } -
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}`; } } -
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); } } -
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(); } -
One Simple Rule If you change a class field and
want the DOM to update, make it tracked. -
• No jQuery Required • JavaScript Classes • Glimmer Components
• Tracked Properties -
<div class="weather"> <h2>Current weather</h2> <address> New York, NY </address> <div
class="temperature"> 55° </div> </div> <WeatherTracker /> weather-tracker.hbs -
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 -
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 -
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 -
<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 -
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 -
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 -
<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 -
• 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 -
Embroider • Faster builds • Asset optimization • Built on
JavaScript modules -
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 -
HBS ES5 CSS Packager • Optimize and compile for the
browser APP.JS APP.CSS -
HBS ES5 CSS Packager • Optimize and compile for the
browser APP.JS APP.CSS webpack Rollup -
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 -
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