Skip to content

Instantly share code, notes, and snippets.

@jamesarosen
Last active May 31, 2018 19:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesarosen/df6755047cf321527238717bd9904d10 to your computer and use it in GitHub Desktop.
Save jamesarosen/df6755047cf321527238717bd9904d10 to your computer and use it in GitHub Desktop.
Ember 2018 Roadmap

Background

I have been working with Ember since the SproutCore days. I have watched a passionate, dedicated team spend the last 7 years continually improving the framweork and its ecosystem. Along the way, many things have changed (almost always for the better), but some things have remained constant.

Many of the things I say below have been said by others before. I particularly recommend reading Matt McManus's Improve the interoperability of the community and the framework and Chris Garrett's Ember as a Component-Service Framework.

Adopts Good Ideas

Ember is open to learning from the broader community. When React showed how virtual DOM tree diffing could be an efficient way of rendering and rerendering components, Ember happily adopted the pattern. Ember and ember-cli have wholly adopted ES2016 and beyond, working babel, async/await, Promise, classes, and even decorators into the ecosystem. Recently, RFC 232 and RFC 268 made some great changes and simplifications to Ember's testing libraries; one key benefit of this work is that Ember tests now look more like tests for any other JavaScript project (that uses QUnit or another supported testing library).

My shortlist for 2018 in the Adopts Good Ideas category:

Custom Elements

The Custom Elements API is perfectly-suited for small, isolated, reusable components: buttons, switches, tooltips, popovers, modals, and more. I'd like to be able to do something like this:

// app/components/my-timestamp.js
import moment from 'moment'

export default class MyTimestamp extends HTMLTimeElement {
  connectedCallback() {
    const time = moment.utc(this.getAttribute('time'))
    if (!time.isValid()) {
      this.classList.add('my-timestamp--invalid')
    }
    this.textContent = time.format('DD MMM YYYY')
    this.setAttribute('datetime', time.format())
  }
}

and

// app/templates/my-page.hbs
Last updated {{my-timestamp time=model.updatedAt}}

More ES2016 and ES2017

The cutting-edge version of a Component in 2018 might look like this:

// app/components/foo-bar.js
import { attribute } from '@ember-decorators/component'
import { classNames } from '@ember-decorators/component'
import Component from '@ember/component'
import { reads } from '@ember-decorators/object/computed'

@classNames('foo-bar')
export default FooBar extends Component {
  @attribute foo = null
  @reads('foo.bar') bar
}

Generators for core types like Component, Route, Service, and Controller` should default to ES2016 classes.

Decorators are still in-flux, so I would understand documenting them as an optional variant for now. Once the spec is solidified, the guides should document them as primary with computed.reads, actions: {}, tagName = '...', and classNameBindings as fallbacks for legacy code.

npm Support

Ember has some support for including non-Ember code. ember-cli has an import that works with AMD-style modules. There's also the ember-shim generator that wraps a globals-style library in a module that can be imported. Lastly, there's ember-browserify for CommonJS modules, but it has a number of limitations: it doesn't work in the test or addon-test-support trees and it doesn't work if the CommonJS module uses certain ES2016+ features.

ember-cli should support, out-of-the-box, require { has } from 'ramda' (which installs as CommonJS, using Rollup to publish from its ES2016 source) and import Popper from 'popper/popper' (which is uses export default).

Go It Alone, then Regroup

There have been many cases Ember has forged ahead alone out of necessity. In the core framework, there's Ember.get, Ember.set, HTMLbars, and Ember.Object.extend. Since Ember's origins, Object.defineProperty and class Foo extends Bar {} have gained mass appeal and Ember has happily reworked itself to support them.

There are other cases, though, where Ember has remained divergent.

AJAX

Despite the widespread support for fetch, ember-ajax still relies on jQuery.ajax at its core and Pretender and ember-cli-mirage mock XMLHttpRequest. I hope that these three libraries (and, through AjaxServiceSupport, ember-data) will move to fetch.

Build Tools

In Ember's early days, the available build tools were quite limited. I often used Make or Rake to build projects. Jo Liss and others working with her rolled up their sleeves and created Broccoli. When Jo first described the idea to me at a hackfest in San Francisco, I remember being amazed by how she had reduced a very complex set of related problems into a single abstraction. Concatenating multiple files into one, transpiling SCSS to CSS or ES2017 to ES2015, and turning a single .js file into a minified .min.js and a related .js.map file could all be expressed as tree transforms. That single abstraction has supported a host of utilities and libraries that cover the vast majority of build-time needs.

Since the creation of Broccoli, webpack and rollup.js have become the dominant build-time tools in the non-Ember world.

For the sake of interoperability and building on the best ideas from the community, my hope for 2018 is that Ember either (a) makes Broccoli much more interoperable with webpack and rollup or (b) adopts one of the more popular solutions as the core of ember-cli.

Address Cruft and Bugs

Ember's stability without stagnation principle means that the framework has been able to shed itself of problematic and confusing APIs when they become costly for the core team to maintain or for users to understand. In Ember's early days, the routing layer was encapsulated in Ember.State, a state-machine library. This existed in the framework for a while after Ember.Route was introduced, then was moved to an addon.

I believe it is time to move Ember.Route (aka @ember/routing/route) to an addon as well. I work on a few fairly large applications and nearly every route would better be expressed as a single async function that has access to the application's owner:

// app/routes/post.js
import { getOwner } from '@ember/application'

export default async function(transition) {
  const owner = getOwner(this)
  const store = owner.lookup('service:store')
  const post = await store.find('post', transition.params.id)
  await RSVP.all([ post.author, post.comments ])
  return owner.lookup('template:post').render({ post })
}

This API would have a number of benefits.

It makes it possible to express a whole (small) page in a single file:

import Handlebars from 'handlebars'
const { escapeExpression } = Handlebars.Utils

export default function({ params: { name }}) {
  return `Welcome, ${escapeExpression(name)}`
}

It eliminates (or moves to an optional API) the magic of beforeModel, model, afterModel, setupController, resetController, and deactivate. I always have to look up which of these send the application into a loading sub-state and which don't, which support returning a Promise and which don't, etc.

Transitioning routes and rendering obeys more functional-programming principles and makes it easier to program with immutability.

It makes Ember easier to understand for programmers coming from React, Express, Sinatra, and many other frameworks.

And lastly, it would make query-params more consistent and predictable. cf RFC 196

@jamesarosen
Copy link
Author

@ef4 just completed an item from my wishlist! https://github.com/ef4/ember-auto-import

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment