How To Use Ember’s New Module Import Syntax Today

One of the more exciting things on the horizon for the Ember.js community is an RFC that was merged early in 2017, RFC #176 Javascript Modules API. I encourage you to read the entire RFC if you want to get fully up to speed, but the high level summary from the RFC lays out its goals:

Make Ember feel less overwhelming to new users, and make Ember applications start faster, by replacing the Emberglobal with a first-class system for importing just the parts of the framework you need.

The RFC goes on to enumerate many benefits to this approach including modernizing Ember’s import ergonomics as well as unlocking tree shaking, a technique for reducing application payload size by eliminating code that is not needed at build time.

While the implementation of this RFC is not actually complete, it is possible for Ember developers who want to be ready for what is coming next to start migrating their applications and addons to use this new modules import API in a backwards compatible way today. Read on to find out how.

How things work today

In today’s Ember apps you may be familiar with a component that imports and destructuresEmber.computed or Ember.inject.service like this:

import Ember from 'ember';
const { computed, inject: { service } } = Ember;
export default Ember.Component.extend({
store: service(),
property: computed(function() {
return 'myprop';
})
});

Migrating to the new Modules API

The new modules API has yet to be implemented in Ember proper, but thanks to the hard work of a bunch of folks in the Ember community it is now possible to very easily migrate your apps to the new modules import API in a backwards compatible way.

The main hard requirements for all of this that your app be using ember-cli-babel 6.6.0 or later, that your app be running Node 6 or later and that you are comfortable doing something that is not yet mainstream in Ember-land.

Using ember-modules-codemod

To ease the transition, a tool for handling the migration automatically, ember-modules-codemod, was created to take care of this for you. Let’s install it and run it against our app:

Be forewarned that running this codemod changes your files in place, so ensure that you are using source control or have a backup of your app before doing this.

npm install ember-modules-codemod -g
cd your-app
ember-modules-codemod

If we run this codemod on our example from above, we can see the difference in that now we are importing Component, computed and service from their respective packages:

import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
export default Component.extend({
store: service(),
property: computed(function() {
return 'myprop';
})
});

Any problems the codemod had migrating your code will be written out to MODULE_REPORT.md. You can check this for any issues that are specific to your code, or you can look for/report any issues you might have encountered in the codemod repo on GitHub.

What if I use ember-cli-shims?

Many folks, anticipating something similar to RFC #176, had transitioned to using ember-cli-shims for their module imports, so their component would look something like this:

import Ember from 'ember';
import computed from 'ember-computed';
import service from 'ember-service/inject';
export default Ember.Component.extend({
store: service(),
property: computed(function() {
return 'myprop';
})
});

Unfortunately, with the acceptance of RFC #176 ember-cli-shims has effectively been deprecated. As of this writing ember-modules-codemod doesn’t support migrating to the new modules API either, but the good news is that there is another way to accomplish the same thing.

There exists a very excellent eslint plugin for ember apps called eslint-plugin-ember. In addition to a slew of recommended rules to keep your Ember apps tidy (including one to enforce the new modules import syntax), it has a rule for disabling the usage of ember-cli-shims-style imports that happens to include a very handy automated tool for fixing anything that happens to violate that rule. Let’s try it out!

First, we need to add the eslint-plugin-ember addon to our app:

yarn add --dev eslint-plugin-ember

Now we can add the no-old-shims rules to our .eslintrc.js file:

plugins: [
'ember'
],
rules: {
// other rules
'ember/no-old-shims': 'error'
}

When we open our component now our editor should complain at us that we are still using old shims for our modules. Turns out this can be automagically rectified using eslint’s handy --fix feature alongside the work of Tobias Bieniek.

./node_modules/.bin/eslint --fix app

And voila, our component now looks exactly the same as the one we migrated using the codemod:

import Component from '@ember/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
export default Component.extend({
store: service(),
property: computed(function() {
return 'myprop';
})
});

You can follow along with both these examples in the commits of this example app

Now what?

It is still early days™ in the migration toward this new import syntax, and I don’t actually know at what point this migration will yield tangible benefits for your applications. However, given the tools available it is relatively easy to at least try out this process on your applications and report any issues sooner than later, and you can also begin getting comfortable with what is coming in the future.

One of the main questions I will be trying to sort out for myself is how best to learn about and teach these new imports to other Ember developers. This list of possible imports is quite lengthy, and while this is mitigated by the fact that editors like Atom already have some built in snippets to make things easier, this is a significant change from what we are used to. I will be curious how others plan to approach this, so if you have any thoughts or ideas do let me know.

Additionally, any other corrections or feedback is appreciated as I am still learning about all this myself ❤️