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

Virtual EmberConf 2022: Platform State of the Union

Virtual EmberConf 2022: Platform State of the Union

Godfrey Chan

April 19, 2022
Tweet

More Decks by Godfrey Chan

Other Decks in Programming

Transcript

  1. PLATFORM
    STATE OF THE UNION
    VERY VIRTUAL SUPER ONLINE

    View Slide

  2. POLARIS
    ALIGNING WITH MODERN PACKAGES

    View Slide

  3. PACKAGES
    HOW DO THEY WORK?
    package.json
    {
    "name": "my-app",
    /* ... */
    "devDependencies": {
    "moment": "^2.29.2"
    /* ... */
    }
    }
    import moment from 'moment';

    View Slide

  4. PACKAGES
    HOW DO THEY WORK?
    import moment from 'moment'; node_modules
    moment
    moment.js

    View Slide

  5. HOW IT ALL BEGAN
    THE “SCRIPT” IN JAVASCRIPT

    View Slide

  6. HOW IT ALL BEGAN
    THE “SCRIPT” IN JAVASCRIPT



    My App







    View Slide

  7. HOW IT ALL BEGAN
    THE “SCRIPT” IN JAVASCRIPT
    app/app.js
    window.App MySelectComponent = Ember.Component.extend({
    /* ... */
    });
    app/router.js
    App.MySelectComponent = Ember.Component.extend({
    /* ... */
    });
    app/components/nav-bar.js
    App.MySelectComponent = Ember.Component.extend({
    /* ... */
    });
    app/components/select-menu.js
    App.MySelectComponent = Ember.Component.extend({
    /* ... */
    });
    app/components/my-select.js
    App.MySelectComponent = Ember.Component.extend({
    /* ... */
    });
    dist/my-app.js
    // app/app.js
    window.App = Ember.Application.create({
    /* ... */
    });
    // app/router.js
    App.Router.map({
    /* ... */
    });
    // app/components/nav-bar.js
    App.NavBarComponent = Ember.Component.extend({
    /* ... */
    });
    // app/components/select-menu.js
    App.SelectMenuComponent = Ember.Component.extend({
    /* ... */
    });
    // app/components/my-select.js
    App.MySelectComponent = Ember.Component.extend({
    /* ... */
    });

    View Slide

  8. app/app.js
    window.App MySelectComponent = Ember.Component.extend({
    /* ... */
    });
    app/router.js
    App.MySelectComponent = Ember.Component.extend({
    /* ... */
    });
    app/components/nav-bar.js
    App.MySelectComponent = Ember.Component.extend({
    /* ... */
    });
    app/components/select-menu.js
    App.MySelectComponent = Ember.Component.extend({
    /* ... */
    });
    app/components/my-select.js
    App.MySelectComponent = Ember.Component.extend({
    /* ... */
    });
    dist/my-app.js
    // app/app.js
    window.App = Ember.Application.create({
    /* ... */
    });
    // app/router.js
    App.Router.map({
    /* ... */
    });
    // app/components/nav-bar.js
    App.NavBarComponent = Ember.Component.extend({
    /* ... */
    });
    // app/components/select-menu.js
    App.SelectMenuComponent = Ember.Component.extend({
    /* ... */
    });
    // app/components/my-select.js
    App.MySelectComponent = Ember.Component.extend({
    /* ... */
    });
    “bundling” “bundle”
    TANGENT

    View Slide

  9. JAVASCRIPT MODULES
    THE NATIVE MODULES SYSTEM
    app/components/my-select.js
    import Ember from 'ember';
    export default Ember.Component.extend({
    classNames: ['my-select'],
    init() {
    this._super();
    this.set('isActive', false);
    },
    click(event) {
    event.preventDefault();
    this.toggleProperty('isActive');
    },
    /* ... */
    });
    app/components/my-select.js
    App.MySelectComponent = Ember.Component.extend({
    classNames: ['my-select'],
    init() {
    this._super();
    this.set('isActive', false);
    },
    click(event) {
    event.preventDefault();
    this.toggleProperty('isActive');
    },
    /* ... */
    });
    !

    View Slide

  10. JAVASCRIPT MODULES
    THE NATIVE MODULES SYSTEM
    app/components/my-select.js
    import Ember from 'ember';
    export default Ember.Component.extend({
    classNames: ['my-select'],
    init() {
    this._super();
    this.set('isActive', false);
    },
    click(event) {
    event.preventDefault();
    this.toggleProperty('isActive');
    },
    /* ... */
    });
    app/components/my-select.js
    App.MySelectComponent = Ember.Component.extend({
    classNames: ['my-select'],
    init() {
    this._super();
    this.set('isActive', false);
    },
    click(event) {
    event.preventDefault();
    this.toggleProperty('isActive');
    },
    /* ... */
    });
    !

    View Slide

  11. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    my-app
    > _
    ember build Bundles

    View Slide

  12. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    app/components/loading-spinner.js
    app/components/date-picker.js
    import Component from '@ember/component';
    import task from 'ember-concurrency';
    import moment from 'moment';
    import ENV from 'my-app/config/environment';
    export default class DatePicker extends Component {
    /* ... */
    }
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });

    View Slide

  13. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    app/components/loading-spinner.js
    app/components/date-picker.js
    import Component from '@ember/component';
    import task from 'ember-concurrency';
    import moment from 'moment';
    import ENV from 'my-app/config/environment';
    export default class DatePicker extends Component {
    /* ... */
    }
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });

    View Slide

  14. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    app/components/loading-spinner.js
    app/components/date-picker.js
    import Component from '@ember/component';
    import task from 'ember-concurrency';
    import moment from 'moment';
    import ENV from 'my-app/config/environment';
    export default class DatePicker extends Component {
    /* ... */
    }
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });

    View Slide

  15. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    app/components/loading-spinner.js
    app/components/date-picker.js
    import Component from '@ember/component';
    import task from 'ember-concurrency';
    import moment from 'moment';
    import ENV from 'my-app/config/environment';
    export default class DatePicker extends Component {
    /* ... */
    }
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });

    View Slide

  16. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    app/components/loading-spinner.js
    app/components/date-picker.js
    import Component from '@ember/component';
    import task from 'ember-concurrency';
    import moment from 'moment';
    import ENV from 'my-app/config/environment';
    export default class DatePicker extends Component {
    /* ... */
    }
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });

    View Slide

  17. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    app/components/loading-spinner.js
    app/components/date-picker.js
    import Component from '@ember/component';
    import task from 'ember-concurrency';
    import moment from 'moment';
    import ENV from 'my-app/config/environment';
    export default class DatePicker extends Component {
    /* ... */
    }
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });

    View Slide

  18. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    app/components/loading-spinner.js
    app/components/date-picker.js
    import Component from '@ember/component';
    import task from 'ember-concurrency';
    import moment from 'moment';
    import ENV from 'my-app/config/environment';
    export default class DatePicker extends Component {
    /* ... */
    }
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });

    View Slide

  19. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    app/components/loading-spinner.js
    app/components/date-picker.js
    import Component from '@ember/component';
    import task from 'ember-concurrency';
    import moment from 'moment';
    import ENV from 'my-app/config/environment';
    export default class DatePicker extends Component {
    /* ... */
    }
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });

    View Slide

  20. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    app/components/loading-spinner.js
    app/components/date-picker.js
    import Component from '@ember/component';
    import task from 'ember-concurrency';
    import moment from 'moment';
    import ENV from 'my-app/config/environment';
    export default class DatePicker extends Component {
    /* ... */
    }
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });

    View Slide

  21. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    app/components/loading-spinner.js
    app/components/date-picker.js
    import Component from '@ember/component';
    import task from 'ember-concurrency';
    import moment from 'moment';
    import ENV from 'my-app/config/environment';
    export default class DatePicker extends Component {
    /* ... */
    }
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function () {
    /* ... */
    });
    define('@ember/component', [], function () {
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */

    View Slide

  22. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function () {
    /* ... */
    });
    define('@ember/component', [], function () {
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    ember-concurrency
    ember-moment
    ember-source

    View Slide

  23. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function () {
    /* ... */
    });
    define('@ember/component', [], function () {
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    ember-concurrency
    ember-moment
    ember-source

    View Slide

  24. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function () {
    /* ... */
    });
    define('@ember/component', [], function () {
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    ember-concurrency
    ember-moment
    ember-source

    View Slide

  25. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function () {
    /* ... */
    });
    define('@ember/component', [], function () {
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    ember-concurrency
    ember-moment
    ember-source

    View Slide

  26. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function () {
    /* ... */
    });
    define('@ember/component', [], function () {
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    ember-concurrency
    ember-moment
    ember-source

    View Slide

  27. EMBER BUILD PIPELINE
    OUR ANSWER TO THE MISSING PIECES
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function () {
    /* ... */
    });
    define('@ember/component', [], function () {
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    ember-concurrency
    moment
    ember-source

    View Slide

  28. PACKAGES
    HOW DO THEY WORK?
    import moment from 'moment'; window.require('moment');
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function () {
    /* ... */
    });
    define('@ember/component', [], function () {
    /* ... */
    });

    View Slide

  29. PACKAGES
    HOW DO THEY WORK?
    import moment from 'moment'; node_modules
    moment
    moment.js

    View Slide

  30. NPM Registry
    package.json
    JavaScript Modules (ESM)
    Modern JavaScript
    Loose Modules
    Node Resolution
    Exports Maps
    TypeScript Types
    Dynamic import()
    Top-level Await
    import.meta
    Import Assertions
    <br/>Import Maps<br/>import.meta.resolve<br/>JSON Modules<br/>CSS Modules<br/>Constructable Stylesheets<br/>Web Bundles<br/>pnpm<br/>yarn<br/>npm<br/>webpack<br/>Rollup<br/>Parcel<br/>Vite<br/>Snowpack<br/>JSPM<br/>unpkg.com<br/>skypack.dev<br/>Workspaces<br/>Editors<br/>TypeScript<br/>GitHub<br/>Storybook<br/>Universal Package<br/>

    View Slide

  31. NPM Registry
    package.json
    JavaScript Modules (ESM)
    Modern JavaScript
    Loose Modules
    Node Resolution
    Exports Maps
    TypeScript Types
    Dynamic import()
    Top-level Await
    import.meta
    Import Assertions
    <br/>Import Maps<br/>import.meta.resolve<br/>JSON Modules<br/>CSS Modules<br/>Constructable Stylesheets<br/>Web Bundles<br/>pnpm<br/>yarn<br/>npm<br/>webpack<br/>Rollup<br/>Parcel<br/>Vite<br/>Snowpack<br/>JSPM<br/>unpkg.com<br/>skypack.dev<br/>Workspaces<br/>Editors<br/>TypeScript<br/>GitHub<br/>Storybook<br/>Universal Package<br/>

    View Slide

  32. POLARIS
    ALIGNING WITH MODERN PACKAGES

    View Slide

  33. GUIDING PRINCIPLES
    ALIGNING WITH MODERN PACKAGES
    1. Addons are universal packages
    2. Imports are standard imports
    3. Express dependencies as imports

    View Slide

  34. ADDONS TODAY
    PUBLISHED AS DYNAMIC BUILD INSTRUCTIONS
    > _
    ember build
    ember-concurrency
    ember-moment
    ember-source
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function
    /* ... */
    });
    define('@ember/component', [], function (
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    });

    View Slide

  35. ADDONS TODAY
    PUBLISHED AS DYNAMIC BUILD INSTRUCTIONS
    > _
    ember build
    ember-concurrency
    ember-moment
    ember-source
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function
    /* ... */
    });
    define('@ember/component', [], function (
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    });

    View Slide

  36. ADDONS TODAY
    PUBLISHED AS DYNAMIC BUILD INSTRUCTIONS
    > _
    ember build
    ember-concurrency
    ember-moment
    ember-source
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function
    /* ... */
    });
    define('@ember/component', [], function (
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    });

    View Slide

  37. ADDONS TODAY
    PUBLISHED AS DYNAMIC BUILD INSTRUCTIONS
    > _
    ember build
    ember-concurrency
    ember-moment
    ember-source
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function
    /* ... */
    });
    define('@ember/component', [], function (
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    });

    View Slide

  38. ADDONS IN POLARIS
    PUBLISHED AS UNIVERSAL PACKAGES
    https://emberjs.github.io/rfcs/0507-embroider-v2-package-format.html
    Universal Packages
    NPM Registry
    package.json
    JavaScript Modules (ESM)
    “Modern” JavaScript Features
    Node Resolution
    Exports Maps
    TypeScript Types

    View Slide

  39. ADDONS IN POLARIS
    PUBLISHED AS UNIVERSAL PACKAGES
    > _
    npm publish
    ember-concurrency
    .npmignore
    package.json
    tsconfig.json
    index.ts

    View Slide

  40. ADDONS IN POLARIS
    PUBLISHED AS UNIVERSAL PACKAGES
    > _
    npm publish
    tsc
    ember-concurrency
    .npmignore
    package.json
    tsconfig.json
    index.ts

    View Slide

  41. ADDONS IN POLARIS
    PUBLISHED AS UNIVERSAL PACKAGES
    ember-concurrency
    package.json
    index.js
    index.d.ts
    ember-concurrency
    .npmignore
    package.json
    tsconfig.json
    index.ts
    > _
    npm publish

    View Slide

  42. GUIDING PRINCIPLES
    ALIGNING WITH MODERN PACKAGES
    1. Addons are universal packages
    2. Imports are standard imports
    3. Express dependencies as imports

    View Slide

  43. STANDARD IMPORTS
    NODE PACKAGE RESOLUTION
    import uniq from 'lodash/uniq';

    View Slide

  44. STANDARD IMPORTS
    NODE PACKAGE RESOLUTION
    import uniq from 'lodash/uniq'; package.json
    {
    "name": "my-app",
    /* ... */
    "devDependencies": {
    "lodash": "^4.0.0"
    /* ... */
    }
    }

    View Slide

  45. STANDARD IMPORTS
    NODE PACKAGE RESOLUTION
    import uniq from 'lodash/uniq'; node_modules
    lodash
    uniq.js

    View Slide

  46. STANDARD IMPORTS
    NODE PACKAGE RESOLUTION
    import uniq from 'lodash/uniq';
    import task from 'ember-concurrency';
    package.json
    {
    "name": "my-app",
    /* ... */
    "devDependencies": {
    "ember-concurrency": "^2.2.1"
    /* ... */
    }
    }

    View Slide

  47. STANDARD IMPORTS
    NODE PACKAGE RESOLUTION
    import uniq from 'lodash/uniq';
    import task from 'ember-concurrency';
    node_modules
    ember-concurrency
    index.js

    View Slide

  48. STANDARD IMPORTS
    NODE PACKAGE RESOLUTION
    import uniq from 'lodash/uniq';
    import task from 'ember-concurrency';
    import { timeout } from 'ember-concurrency/utils';
    // ERROR!
    import nope from 'ember-concurrency/private';
    package.json
    {
    "name": "ember-concurrency",
    /* ... */
    "exports": {
    ".": "./src/index.js",
    "./utils": "./src/concurrency-utils.js"
    }
    }
    Using exports Map
    https://nodejs.org/api/packages.html#subpath-exports

    View Slide

  49. View Slide

  50. EMBROIDER
    A NEW BUILD PIPELINE?
    Powered By
    > _
    ember build
    my-app Bundles

    View Slide

  51. EMBROIDER
    A NEW BUILD ARCHITECTURE
    Powered By
    > _
    ember build
    ember-concurrency
    ember-moment
    ember-source
    my-app Bundles

    View Slide

  52. EMBROIDER
    A NEW BUILD ARCHITECTURE
    Powered By
    > _
    ember build
    ember-concurrency
    ember-moment
    ember-source
    my-app
    Bundles

    View Slide

  53. TREE SHAKING
    LAZY DEPENDENCY INCLUSION
    node_modules
    ember-concurrency
    index.js
    lodash
    unescape.js
    union.js
    unionBy.js
    unionWith.js
    uniq.js
    unionBy.js
    unionWith.js

    View Slide

  54. TREE SHAKING
    LAZY DEPENDENCY INCLUSION
    node_modules
    ember-concurrency
    index.js
    lodash
    unescape.js
    union.js
    unionBy.js
    unionWith.js
    uniq.js
    unionBy.js
    unionWith.js
    app.js

    View Slide

  55. TREE SHAKING
    LAZY DEPENDENCY INCLUSION
    node_modules
    ember-concurrency
    index.js
    lodash
    unescape.js
    union.js
    unionBy.js
    unionWith.js
    uniq.js
    unionBy.js
    unionWith.js
    app.js
    import task from 'ember-concurrency';

    View Slide

  56. TREE SHAKING
    LAZY DEPENDENCY INCLUSION
    node_modules
    ember-concurrency
    index.js
    lodash
    unescape.js
    union.js
    unionBy.js
    unionWith.js
    uniq.js
    unionBy.js
    unionWith.js
    app.js
    import task from 'ember-concurrency';
    import uniq from 'lodash/uniq';

    View Slide

  57. TREE SHAKING
    LAZY DEPENDENCY INCLUSION
    node_modules
    ember-concurrency
    index.js
    lodash
    unescape.js
    union.js
    unionBy.js
    unionWith.js
    uniq.js
    unionBy.js
    unionWith.js
    app.js
    import task from 'ember-concurrency';
    import uniq from 'lodash/uniq';
    import Tracker from './utils/tracker';

    View Slide

  58. TREE SHAKING
    LAZY DEPENDENCY INCLUSION
    node_modules
    ember-concurrency
    index.js
    lodash
    unescape.js
    union.js
    unionBy.js
    unionWith.js
    uniq.js
    unionBy.js
    unionWith.js
    app.js
    import task from 'ember-concurrency';
    import uniq from 'lodash/uniq';
    import Tracker from './utils/tracker';
    utils/tracker.js

    View Slide

  59. TREE SHAKING
    LAZY DEPENDENCY INCLUSION
    node_modules
    ember-concurrency
    index.js
    lodash
    unescape.js
    union.js
    unionBy.js
    unionWith.js
    uniq.js
    unionBy.js
    unionWith.js
    app.js
    import task from 'ember-concurrency';
    import uniq from 'lodash/uniq';
    import Tracker from './utils/tracker';
    utils/tracker.js
    import union from 'lodash/union';

    View Slide

  60. EMBROIDER
    A NEW BUILD ARCHITECTURE
    Outsourced To
    Powered By
    > _
    ember build
    my-app Bundles

    View Slide

  61. EMBROIDER
    A PLUGGABLE BUILD ARCHITECTURE
    Outsourced To
    Powered By
    > _
    ember build
    my-app Bundles

    View Slide

  62. GUIDING PRINCIPLES
    ALIGNING WITH MODERN PACKAGES
    1. Addons are universal packages
    2. Imports are standard imports
    3. Express dependencies as imports

    View Slide

  63. TREE SHAKING TODAY
    COULD IT BE DONE?
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function () {
    /* ... */
    });
    define('@ember/component', [], function () {
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    });
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });

    View Slide

  64. TREE SHAKING TODAY
    COULD IT BE DONE?
    dist/vendor.js
    define('ember-concurrency', [
    '@ember/application',
    '@ember/object',
    '@ember/object/computed',
    /* ... */
    ], function () {
    /* ... */
    });
    define('moment', [], function () {
    /* ... */
    });
    define('@ember/application', [], function () {
    /* ... */
    });
    define('@ember/component', [], function () {
    /* ... */
    });
    define('@ember/object', [], function () {
    /* ... */
    });
    dist/my-app.js
    /* ... */
    define('my-app/components/loading-spinner', [], function() {
    /* ... */
    });
    define('my-app/components/date-picker', [
    '@ember/component',
    'ember-concurrency',
    'moment',
    'my-app/config/environment'
    ],function(Component, task, moment, ENV) {
    class DatePicker extends Component {
    /* ... */
    }
    return DatePicker;
    });
    define('my-app/config/environment', [], function () {
    return {
    environment: 'development',
    modulePrefix: 'my-app',
    /* ... */
    };
    });

    View Slide

  65. TREE SHAKING TODAY
    COULD IT BE DONE?
    app/router.js
    import EmberRouter from '@ember/routing/router';
    export default class Router extends EmberRouter {}
    Router.map(function () {
    this.route('post');
    });

    View Slide

  66. TREE SHAKING TODAY
    COULD IT BE DONE?
    app/router.js
    import EmberRouter from '@ember/routing/router';
    export default class Router extends EmberRouter {}
    Router.map(function () {
    this.route('post');
    });
    app/controllers/post.js
    import Controller from '@ember/controller';
    export default class PostController extends Controller {
    /* ... */
    }
    app/routes/post.js
    import Route from '@ember/routing/route';
    import { service } from '@ember/service';
    export default class PostRoute extends Route {
    @service store;
    async model(params) {
    this.store.find('post', params.post_id);
    }
    }

    View Slide

  67. TREE SHAKING TODAY
    COULD IT BE DONE?
    app/router.js
    import EmberRouter from '@ember/routing/router';
    export default class Router extends EmberRouter {}
    Router.map(function () {
    this.route('post');
    });
    app/controllers/post.js
    import Controller from '@ember/controller';
    export default class PostController extends Controller {
    /* ... */
    }
    app/routes/post.js
    import Route from '@ember/routing/route';
    import { service } from '@ember/service';
    export default class PostRoute extends Route {
    @service store;
    async model(params) {
    this.store.find('post', params.post_id);
    }
    }

    View Slide

  68. EMBROIDER
    ENCODING EMBER CONVENTIONS
    Outsourced To
    Powered By
    my-app Bundles
    my-app

    View Slide

  69. BAD IDEA

    View Slide

  70. TEMPLATES TODAY
    NAME BASED RESOLUTIONS
    app/components/welcome.hbs

    Hello there, welcome back!

    View Slide

  71. TEMPLATES TODAY
    NAME BASED RESOLUTIONS
    app/components/welcome.hbs

    Hi there, welcome back!

    Rendering a component named "hello" RFC #311

    View Slide

  72. TEMPLATES TODAY
    NAME BASED RESOLUTIONS
    app/components/welcome.hbs

    Hi there, welcome back!

    Rendering a component named "hello"
    Step 1. Find the component
    RFC #311

    View Slide

  73. TEMPLATES TODAY
    NAME BASED RESOLUTIONS
    app/components/welcome.hbs

    Hi there, welcome back!

    Rendering a component named "hello"
    Step 1. Find the component
    owner.factoryFor("component:hello");
    RFC #311

    View Slide

  74. TEMPLATES TODAY
    NAME BASED RESOLUTIONS
    app/components/welcome.hbs

    Hi there, welcome back!

    Rendering a component named "hello"
    Step 1. Find the component
    owner.factoryFor("component:hello");
    require("my-app/components/hello");
    RFC #311
    ember-resolver

    View Slide

  75. TEMPLATES TODAY
    NAME BASED RESOLUTIONS
    app/components/welcome.hbs

    Hi there, welcome back!

    Rendering a component named "hello"
    Step 1. Find the component
    owner.factoryFor("component:hello");
    require("my-app/components/hello");
    (load the AMD module from app bundle)
    RFC #311
    ember-resolver
    loader.js

    View Slide

  76. TEMPLATES TODAY
    NAME BASED RESOLUTIONS
    app/components/welcome.hbs

    Hi there, welcome back!

    Rendering a component named "hello"
    Step 1. Find the component
    owner.factoryFor("component:hello");
    require("my-app/components/hello");
    (load the AMD module from app bundle)
    Step 2. Setup the component
    Manager = getComponentManager(Hello);
    Template = getComponentTemplate(Hello);
    C = Manager.createComponent(Hello, Args);
    This = Manager.getContext(C);
    Step 3. Render
    render(Template, This, Args);
    document.createElement(!!"), etc
    RFC #311
    ember-resolver
    loader.js
    RFC #213 Component Manager
    RFC #481 Co-location
    glimmer-vm

    View Slide

  77. TEMPLATES TODAY
    NAME BASED RESOLUTIONS
    app/components/welcome.hbs

    Hi there, welcome back!

    Rendering a component named "hello"
    Step 1. Find the component
    owner.factoryFor("component:hello");
    require("my-app/components/hello");
    (load the AMD module from app bundle)
    Step 2. Setup the component
    Manager = getComponentManager(Hello);
    Template = getComponentTemplate(Hello);
    C = Manager.createComponent(Hello, Args);
    This = Manager.getContext(C);
    Step 3. Render
    render(Template, This, Args);
    document.createElement(!!"), etc
    RFC #311
    ember-resolver
    loader.js
    RFC #213 Component Manager
    RFC #481 Co-location
    glimmer-vm

    View Slide

  78. TEMPLATES TODAY
    NAME BASED RESOLUTIONS
    app/components/welcome.hbs

    Hi there, welcome back!

    Rendering a component named "hello"
    Step 1. Find the component
    owner.factoryFor("component:hello");
    require("my-app/components/hello");
    (load the AMD module from app bundle)
    Step 2. Setup the component
    Manager = getComponentManager(Hello);
    Template = getComponentTemplate(Hello);
    C = Manager.createComponent(Hello, Args);
    This = Manager.getContext(C);
    Step 3. Render
    render(Template, This, Args);
    document.createElement(!!"), etc
    RFC #311
    ember-resolver
    loader.js
    RFC #213 Component Manager
    RFC #481 Co-location
    glimmer-vm

    View Slide

  79. TEMPLATES TODAY
    NAME BASED RESOLUTIONS
    app/components/welcome.hbs

    Hi there, welcome back!

    {{log Hello}}
    ~~~~~
    Error. Hello is not a value!
    Rendering a component named "hello"
    Step 1. Find the component
    owner.factoryFor("component:hello");
    require("my-app/components/hello");
    (load the AMD module from app bundle)
    Step 2. Setup the component
    Manager = getComponentManager(Hello);
    Template = getComponentTemplate(Hello);
    C = Manager.createComponent(Hello, Args);
    This = Manager.getContext(C);
    Step 3. Render
    render(Template, This, Args);
    document.createElement(!!"), etc
    RFC #311
    ember-resolver
    loader.js
    RFC #213 Component Manager
    RFC #481 Co-location
    glimmer-vm

    View Slide

  80. TEMPLATES TODAY
    NAME BASED RESOLUTIONS
    app/components/welcome.hbs


    Hi there, welcome back!

    {{!-- Works. Hello is a value! --}}
    {{log Hello}}

    Rendering the value Hello as a component
    Step 1. Find the component
    owner.factoryFor("component:hello");
    require("my-app/components/hello");
    (load the AMD module from app bundle)
    Step 2. Setup the component
    Manager = getComponentManager(Hello);
    Template = getComponentTemplate(Hello);
    C = Manager.createComponent(Hello, Args);
    This = Manager.getContext(C);
    Step 3. Render
    render(Template, This, Args);
    document.createElement(!!"), etc
    RFC #64 Contextual Components
    ember-resolver
    loader.js
    RFC #213 Component Manager
    RFC #481 Co-location
    glimmer-vm
    Skipped!

    View Slide

  81. TEMPLATES IN POLARIS
    VALUES EVERYWHERE
    app/components/welcome.gjs
    import Hello from 'my-app/components/hello';


    Hi there, welcome back!

    {{!-- Works. Hello is a value! --}}
    {{log Hello}}

    RFC #373 Modifier Manager
    RFC #432 Contextual Helpers/Modifiers
    RFC #496 Strict Mode Templates
    Refactor Handlebars parser and glimmer-vm
    RFC #625 Helper Manager
    RFC #756 Default Helper Manager
    RFC #779

    View Slide

  82. TEMPLATES IN POLARIS
    VALUES EVERYWHERE
    app/components/welcome.gjs
    import Hello from 'my-app/components/hello';


    Hi there, welcome back!

    {{!-- Works. Hello is a value! --}}
    {{log Hello}}

    RFC #373 Modifier Manager
    RFC #432 Contextual Helpers/Modifiers
    RFC #496 Strict Mode Templates
    Refactor Handlebars parser and glimmer-vm
    RFC #625 Helper Manager
    RFC #756 Default Helper Manager
    RFC #779

    View Slide

  83. TEMPLATES IN POLARIS
    VALUES EVERYWHERE
    app/components/welcome.gjs
    import Hello from 'my-app/components/hello';
    // Same value inside and outside the template
    console.log(Hello);


    Hi there, welcome back!

    {{!-- Works. Hello is a value! --}}
    {{log Hello}}

    RFC #373 Modifier Manager
    RFC #432 Contextual Helpers/Modifiers
    RFC #496 Strict Mode Templates
    Refactor Handlebars parser and glimmer-vm
    RFC #625 Helper Manager
    RFC #756 Default Helper Manager
    RFC #779

    View Slide

  84. TEMPLATES IN POLARIS
    VALUES EVERYWHERE
    app/components/welcome.gjs
    import Hello from 'my-app/components/hello';
    const GREETING = 'Hi there';


    {{GREETING}}, welcome back!


    RFC #373 Modifier Manager
    RFC #432 Contextual Helpers/Modifiers
    RFC #496 Strict Mode Templates
    Refactor Handlebars parser and glimmer-vm
    RFC #625 Helper Manager
    RFC #756 Default Helper Manager
    RFC #779

    View Slide

  85. TEMPLATES IN POLARIS
    VALUES EVERYWHERE
    app/components/welcome.gjs
    import Hello from 'my-app/components/hello';
    /**
    * Randomly chosen a value for the given choices.
    */
    function sample(...choices) {
    let i = Math.floor(Math.random() * choices.length);
    return choices[i];
    }


    Hi there, welcome back!

    {{sample 'You won a prize!' 'Better luck next time!'}}

    RFC #373 Modifier Manager
    RFC #432 Contextual Helpers/Modifiers
    RFC #496 Strict Mode Templates
    Refactor Handlebars parser and glimmer-vm
    RFC #625 Helper Manager
    RFC #756 Default Helper Manager
    RFC #779

    View Slide

  86. TEMPLATES IN POLARIS
    VALUES EVERYWHERE
    app/components/welcome.gjs
    import Hello from 'my-app/components/hello';
    import sample from 'my-app/helpers/sample';


    Hi there, welcome back!

    {{sample 'You won a prize!' 'Better luck next time!'}}

    RFC #373 Modifier Manager
    RFC #432 Contextual Helpers/Modifiers
    RFC #496 Strict Mode Templates
    Refactor Handlebars parser and glimmer-vm
    RFC #625 Helper Manager
    RFC #756 Default Helper Manager
    RFC #779

    View Slide

  87. TEMPLATES IN POLARIS
    VALUES EVERYWHERE
    app/components/welcome.gjs
    import Hello from 'my-app/components/hello';
    import sample from 'lodash/sample';
    import { array } from '@ember/helper';


    Hi there, welcome back!

    {{sample (array
    'You won a prize!'
    'Better luck next time!'
    )}}

    RFC #373 Modifier Manager
    RFC #432 Contextual Helpers/Modifiers
    RFC #496 Strict Mode Templates
    Refactor Handlebars parser and glimmer-vm
    RFC #625 Helper Manager
    RFC #756 Default Helper Manager
    RFC #779

    View Slide

  88. TEMPLATES IN POLARIS
    VALUES EVERYWHERE
    app/components/welcome.gjs
    import Component from '@glimmer/component';
    import { tracked } from '@glimmer/tracking';
    import { action } from '@ember/object';
    import Hello from 'my-app/components/hello';
    import sample from 'lodash/sample';
    export default class Welcome extends Component {
    @tracked result = this.lottery();
    @action retry() {
    this.result = this.lottery();
    }
    lottery() {
    return sample(['You won a prize!', 'Better luck next time!']);
    }

    Hi there, welcome back!
    {{this.result}}

    }
    RFC #373 Modifier Manager
    RFC #432 Contextual Helpers/Modifiers
    RFC #496 Strict Mode Templates
    Refactor Handlebars parser and glimmer-vm
    RFC #625 Helper Manager
    RFC #756 Default Helper Manager
    RFC #779

    View Slide

  89. TANGENT
    Is this!!" JSX?!

    View Slide

  90. TANGENT

    View Slide

  91. GUIDING PRINCIPLES
    ALIGNING WITH MODERN PACKAGES
    1. Addons are universal packages
    2. Imports are standard imports
    3. Express dependencies as imports

    View Slide

  92. POLARIS
    HOW DO WE GET THERE?

    View Slide

  93. View Slide

  94. View Slide

  95. View Slide

  96. View Slide

  97. Blueprints
    POLARIS
    Deprecations
    Migration Guides Codemods
    Guides
    Tutorial
    Blog Posts
    Outreach
    Best Practices
    Bug Reports
    Examples
    Primitives
    High-level Features
    Lint Rules
    Paradigms
    Patterns
    Cookbooks
    Discussions
    Talks
    Experience Reports
    Abstractions
    Tools
    Libraries
    Integrations
    Ember Inspector
    RFCs

    View Slide