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. Code of Conduct

    View full-size slide

  2. A framework for creating
    ambitious web applications.

    View full-size slide

  3. Climb the
    Mountain
    Together

    View full-size slide

  4. Climb the
    Mountain
    Together

    View full-size slide

  5. The
    Celery
    Test

    View full-size slide

  6. • Ember CLI
    • Six-Week Release
    Cycle
    • RFC Process
    • Code Mods
    • Templates
    • Engage with
    Standards
    • Invest in Community
    • Addons
    What We Did

    View full-size slide

  7. • tag<br/>• Web Components<br/>• Framework reboot<br/>• Ember Native<br/>What We Didn’t Do<br/>

    View full-size slide

  8. Progress
    Stability

    View full-size slide

  9. Aggressive Changes Cautious Changes Aggressive Changes

    View full-size slide

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

    View full-size slide

  11. 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

    View full-size slide

  12. Falling Behind
    Community Fragmenting
    Em
    ber 1.13
    Em
    ber 2.0

    View full-size slide

  13. Falling Behind
    Community Fragmenting
    Em
    ber 2.0

    View full-size slide

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

    View full-size slide

  15. {{user-avatar user=this.currentUser}}

    Co

    View full-size slide

  16. {{input value="hello"}}

    Co

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  19. Where We Are Where We
    Want to Be

    View full-size slide

  20. Where We Are
    Where We
    Want to Be

    View full-size slide

  21. Ready for Everyone
    Where We Are
    Where We
    Want to Be

    View full-size slide

  22. • No jQuery Required
    • JavaScript Classes
    • Angle Bracket
    Invocation
    • Glimmer
    Components
    • Tracked Properties
    Ember Octane
    A better component system

    View full-size slide

  23. • Off by default for new apps
    • Documentation, tests,
    addons updated to not
    need jQuery
    No jQuery
    Required

    View full-size slide

  24. No jQuery
    Required

    View full-size slide

  25. • Standard JavaScript syntax
    • Knowledge portability
    • Integration with IDEs, linters
    • Works well with TypeScript
    JavaScript
    Classes

    View full-size slide

  26. 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

    View full-size slide

  27. 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;
    }
    }
    }

    View full-size slide

  28. 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

    View full-size slide

  29. 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

    View full-size slide

  30. 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

    View full-size slide

  31. 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

    View full-size slide

  32. 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;
    }
    }
    }

    View full-size slide

  33. 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

    View full-size slide

  34. 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

    View full-size slide

  35. Angle Bracket
    Invocation
    {{weather-tracker zip="10001"}}

    View full-size slide

  36. Angle Bracket
    Invocation
    @zip="10001"
    aria-label="Weather" />

    View full-size slide

  37. • Easier to scan visually
    • One-word components

    • HTML attributes
    Angle Bracket
    Invocation

    View full-size slide

  38. • Minimal base class
    • “Outer HTML” templates
    • Explicit, immutable
    arguments
    Glimmer
    Components

    View full-size slide

  39. 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

    View full-size slide

  40. id="my-button"
    class="btn {{if @isDangerous 'dangerous'}}"
    disabled={{@isDisabled}}>
    Click Me

    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

    View full-size slide

  41. • State of the art change
    tracking
    • Fast, efficient updates
    • “Just JavaScript”
    • No need for this.set()
    Tracked
    Properties

    View full-size slide

  42. 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++;
    }
    }

    View full-size slide

  43. 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}`;
    }
    }

    View full-size slide

  44. 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);
    }
    }

    View full-size slide

  45. person = new Person("Godfrey", "Chan", 1988);
    1. Create a new model instance
    2. Pass it to a component

    3. Mutate the model from the component
    @action
    randomize() {
    this.args.person.randomizeAge();
    }

    View full-size slide

  46. One Simple Rule
    If you change a class field and want the
    DOM to update, make it tracked.

    View full-size slide

  47. • No jQuery Required
    • JavaScript Classes
    • Glimmer
    Components
    • Tracked Properties

    View full-size slide


  48. Current weather

    New York, NY


    55°



    weather-tracker.hbs

    View full-size slide

  49. import Component from '@glimmer/component';
    import { tracked } from '@glimmer/tracking';
    export default class extends Component {
    @tracked temperature = 55;
    @tracked city = "New York, NY";
    }

    Current weather

    {{this.city}}


    {{this.temperature}}°



    weather-tracker.hbs weather-tracker.js

    View full-size slide

  50. 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;
    }
    }

    Current weather

    {{this.city}}


    {{this.temperature}}°



    weather-tracker.hbs weather-tracker.js

    View full-size slide

  51. 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;
    }
    }

    Current weather

    {{this.city}}


    {{this.temperature}}°



    weather-tracker.hbs weather-tracker.js

    View full-size slide


  52. Current weather

    {{this.city}}


    {{this.temperature}}°



    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

    View full-size slide

  53. 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;
    }
    }

    Current weather

    {{this.city}}


    {{this.temperature}}°



    weather-tracker.hbs weather-tracker.js

    View full-size slide

  54. 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;
    }
    }

    Current weather

    {{this.city}}


    {{this.temperature}}°



    weather-tracker.hbs weather-tracker.js

    View full-size slide


  55. Current weather

    New York, NY


    55°


    HTML

    Current weather

    {{this.city}}


    {{this.temperature}}°


    Template
    DOM Output

    View full-size slide

  56. P R E V I E W
    Today

    View full-size slide

  57. • 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

    View full-size slide

  58. Classic Octane ?

    View full-size slide

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

    View full-size slide

  60. Embroider
    • Faster builds
    • Asset optimization
    • Built on JavaScript
    modules

    View full-size slide

  61. 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

    View full-size slide

  62. HBS
    ES5 CSS
    Packager
    • Optimize and compile for the browser
    APP.JS APP.CSS

    View full-size slide

  63. HBS
    ES5 CSS
    Packager
    • Optimize and compile for the browser
    APP.JS APP.CSS
    webpack Rollup

    View full-size slide

  64. Climb the
    Mountain
    Together

    View full-size slide

  65. 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

    View full-size slide

  66. 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

    Async leak detection
    New guides TOC
    Editions
    JS classes
    Rehydration Async testing
    Reader questions
    Public infra redux
    Babel parallel compilation
    Roadmap RFC

    View full-size slide