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.
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:
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
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.
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 import
ed. 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
).
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.
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
.
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.
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
@ef4 just completed an item from my wishlist! https://github.com/ef4/ember-auto-import