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. View Slide

  2. Code of Conduct

    View Slide

  3. View Slide

  4. A framework for creating
    ambitious web applications.

    View Slide

  5. Climb the
    Mountain
    Together

    View Slide

  6. Climb the
    Mountain
    Together

    View Slide

  7. The
    Celery
    Test

    View Slide

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

    View Slide

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

    View Slide

  10. Progress
    Stability

    View Slide

  11. Aggressive Changes Cautious Changes Aggressive Changes

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  15. Falling Behind
    Community Fragmenting
    Em
    ber 2.0

    View Slide

  16. Ember 3.0

    View Slide

  17. Co

    View Slide

  18. Co

    View Slide

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

    View Slide

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

    Co

    View Slide

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

    Co

    View Slide

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

    View Slide

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

    View Slide

  24. Where We Are Where We
    Want to Be

    View Slide

  25. Where We Are
    Where We
    Want to Be

    View Slide

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

    View Slide

  27. View Slide

  28. OCTANE

    View Slide

  29. OCTANE

    View Slide

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

    View Slide

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

    View Slide

  32. No jQuery
    Required

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    • HTML attributes
    Angle Bracket
    Invocation

    View Slide

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

    View Slide

  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

    View Slide

  48. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  53. 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 Slide

  54. View Slide

  55. View Slide

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

    View Slide

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

    View Slide

  58. View Slide


  59. Current weather

    New York, NY


    55°



    weather-tracker.hbs

    View Slide

  60. 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 Slide

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

    Current weather

    {{this.city}}


    {{this.temperature}}°



    weather-tracker.hbs weather-tracker.js

    View Slide

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

    Current weather

    {{this.city}}


    {{this.temperature}}°



    weather-tracker.hbs weather-tracker.js

    View Slide


  63. 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 Slide

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

    Current weather

    {{this.city}}


    {{this.temperature}}°



    weather-tracker.hbs weather-tracker.js

    View Slide

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

    Current weather

    {{this.city}}


    {{this.temperature}}°



    weather-tracker.hbs weather-tracker.js

    View Slide


  66. Current weather

    New York, NY


    55°


    HTML

    Current weather

    {{this.city}}


    {{this.temperature}}°


    Template
    DOM Output

    View Slide

  67. View Slide

  68. FUN

    View Slide

  69. P R E V I E W
    Today

    View Slide

  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

    View Slide

  71. View Slide

  72. Classic Octane ?

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  77. View Slide

  78. View Slide

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

    View Slide

  80. 2019
    Roadmap

    View Slide

  81. Climb the
    Mountain
    Together

    View Slide

  82. View Slide

  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

    View Slide

  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

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

    View Slide

  85. Thank you!

    View Slide