Ember Addons have come long way since being introduced to the community in the Summer of 2014. With a thriving ecosystem and a significantly matured API, they've become the pattern for sharing reusable code across projects, across teams, and across the community. They've become essential.

But as with any fast-moving spec, Ember Addon enlightenment still exists in a state of transparent-but-incomplete documentation and translucent-but-trustworthy field savvy. Ember CLI's public documentation for Developing Addons and Blueprints is the community's official example of the former, and attempts a broad, comprehensive scope. However, this article will attempt to highlight some of the finer points -- the "pro-tips", the "secrets". It aims to be a checklist, of sorts, for things to keep in mind as you begin development that could dramatically improve your overall workflow, architecture, and clarity of code -- you know... Developer Ergonomics™.

So let's get started.

Ribbons and Wrapping for Your package.json

First and foremost, Ember addons are NPM packages. This means that the same steps you should take for defining any other Node application in your package.json file before publishing it apply here as well.

For instance...

Also, keep in mind your keywords. À la heading tags in markup, proper use of these in your package.json helps users search for your addon on the NPM Registry or, more likely, on Ember Observer -- a fantastic project that's helped many an embereño find just the right addon for their project (or saved them from making Yet Another).

Furthermore, a subtle internal optimization that's easy to overlook is removing ember-data as a devDependency if your addon isn't using it. Ember CLI's default addon generator will include it by default, but if it's not needed, feel free to run a quick npm uninstall --save-dev ember-data.

Continuous Integration Domination

There's a reason that a .travis.yml file is generated with every Ember app and addon: Travis CI is a free CI service for public GitHub repositories, and getting it running for a project is literally as simple as flipping a switch on your account's dashboard.

But that's merely Step 1. To utilize CI to its full potential and avoid the feeling of being in a Rube Goldberg cartoon along the way, it's crucial to have common patterns that can merely be plugged in from the start. Let's look a few recipes.

Node Accountability

If you're using a brand new ECMAScript feature in a portion of addon code that's going to be run in Node, you'll likely need to make sure Travis can run against that version when it builds. Achieving this is as straightforward as dialing up your desired numbers:

language: node_js  
node_js:  
  - "4"
  - "5"
  - "6"
  - "6.1"

ember-try Scenarios

Travis runs on the concept of generating builds, and it's useful to know how to conceptualize the Travis build process as a matrix. That's a bit beyond our scope here (but not here), but in short, each item in the matrix constitutes a build, and a main way to initialize multiple items is by writing multiple lines into the env setting.

In the case of addons, we can build our matrix around multiple ember-try scenarios during deployments. Ember Try allows is an Ember CLI addon that tests our code against multiple Bower and NPM dependencies such as ember and ember-data, and its tremendous utility has led to it being baked right into Ember CLI as of 2.6.0. While the default setup created by running ember addon {{ADDON_NAME}} does well to cover the fundamentals, at the current juncture of Ember, it's super straightforward to test against 1.13, every minor 2.x version, and distributions currently out on the release, beta, and canary channels.

env:  
  - EMBER_TRY_SCENARIO=ember-1.13
  - EMBER_TRY_SCENARIO=ember-2.0
  - EMBER_TRY_SCENARIO=ember-2.1
  - EMBER_TRY_SCENARIO=ember-2.2
  - EMBER_TRY_SCENARIO=ember-2.3
  - EMBER_TRY_SCENARIO=ember-2.4
  - EMBER_TRY_SCENARIO=ember-2.5
  - EMBER_TRY_SCENARIO=ember-2.6
  - EMBER_TRY_SCENARIO=ember-release
  - ALLOW_DEPRECATIONS=true EMBER_TRY_SCENARIO=ember-beta
  - ALLOW_DEPRECATIONS=true EMBER_TRY_SCENARIO=ember-canary

Furthermore, you can configure your Travis matrix at a high level using the matrix property, which, for example, comes in handy for giving special treatment to various failures:

matrix:  
  fast_finish: true
  allow_failures:
    - env: ALLOW_DEPRECATIONS=true EMBER_TRY_SCENARIO=ember-canary

Chrome all the things

PhantomJS is a fantastic tool. However, as anyone whose deployed projects with a sizable test suite can assert (sorry), its support and stability around the features of modern web applications can be... inconsistent at best -- as with its ability to helpfully debug such issues when they occur.

Fortunately, there's a better solution: headless Chrome.

Not too long ago not a thing in Travis, recent patterns have emerged among its user base to make Chrome integration seamless. The steps that I've found success with are as follows:

Require sudo for installing the "Trusty" distribution of Ubuntu:

sudo: required  
dist: trusty  

Declare Chrome as an addon and specify the stable version:

addons:  
  apt:
    sources:
    - google-chrome
    packages:
    - google-chrome-stable

Make sure Chrome has the proper setup with a few before_install commands:

before_install:  
  - "export DISPLAY=:99.0"
  - "sh -e /etc/init.d/xvfb start"
  - ...Commands that you already had...

From there, just head over your testem.js file and ask for Chrome in CI -- and you're good to go:

module.exports = {  
  "framework": "qunit",
  "test_page": "tests/index.html?hidepassed",
  "disable_watching": true,
  "launch_in_ci": [
    "Chrome"
  ],
  "launch_in_dev": [
    "Chrome"
  ]
};

Badging

From providing important indications about the project's current state to serving as a shiny chest of medals, Markdown badges are one of the first things someone will see when reading your documentation. The following are the badges that I've found to be the most important for my addons (or when viewing someone else's addon):

And, because the most challenging aspect of badging can be finding the right URL to use for everything, here's a template for the items above.

Fantastic Styles and Where to Find Them

Most Ember addons won't be concerned with styling (and even if it's tempting, it may not be the wisest approach), but sometimes the functionality that you're packaging is inextricably linked to doing things in CSS.

So where do we place our stylesheets? And how do we build them once we do? Currently, styles are a scarcely documented aspect of addon architecture, but for placement, the approach isn't too different than your scripts or templates: Just use the addon's app directory...

app/styles/{{YOUR_ADDON_NAME}}.css  

The tricky part comes when we consider how our stylesheets are processed.

Because Ember CLI allows for styles to be written in SCSS, Stylus, or CSS directly (For anyone curious about writing and processing PostCSS, and example can be found here.), it's worth having our styles imported as vendor styles during the build process and bundled into the app's final vendor.css file. Otherwise, a consuming app would need to perform a manual import inside of their app.css (or .scss or .styl) file -- and there's no guarantee of the format matching.

Taking the example of a file at app/styles/{{YOUR_ADDON_NAME}}.css, we can do the following in the included hook of the addon's index.js file:

included: function(app)  {  
    this._super.included.apply(this, arguments);

    app.import('vendor/{{YOUR_ADDON_NAME}}.css');
}

For even more information, Bill Heaton (AKA pixelhandler) wrote a terrific post on his blog that not only sheds a lot more light on the subject of addon styles, but also demonstrates how to set up a PostCSS pipeline as part of the Broccoli build process.

FastBoot Compatibility

It's a beautiful time to be alive: Ember FastBoot-enabled apps have finally made their way out into production, and their presence will only grow in the future. For addon authors, this means that ensuring compatibility with FastBoot should be on your checklist right alongside ensuring compatibility with various versions of Ember itself.

Fortunately, FastBoot is easy to detect -- a FastBoot global will be exposed any time an application is running in FastBoot mode. Using that knowledge, you can take any code that might be directly tied to the DOM (or more generally, a client)...

didTransition() {  
  document.title = 'My Profile';
}

...and guard it like so:

didTransition() {  
  if (typeof FastBoot === 'undefined') {
    document.title = 'My Profile';
  }
}

Default Blueprints

The same way Ember has Component blueprints, Route blueprints, and the like, when a user runs ember g your-addon-name, they're running what's known as the addon's default blueprint. What makes the default blueprint special, however, is that it's also run automatically when a user first runs ember install your-addon-name. In other words, more often than not, this is your shot to do some really useful stuff. And it's never good to throw away your shot.

Let's look at just a few of the scenarios where -- by exporting an Object to extend the Blueprint class at /blueprints/{{ADDON_NAME}}/index.js --
-- customizing the default blueprint can become a critical part of our addon's development process.

normalizeEntityName

The hero we all need, even if the reason we deserve it can be a bit opaque, normalizeEntityName needs to be overridden as a noop inside of the default blueprint in order for ember g your-addon-name to even work. Just set this as the first property of your exported object, and forget all about it (but never forget it in the future 😀):

module.exports = {

  normalizeEntityName: function() {},

  ...

};
addPackageToProject (and its variants)

Let's say your addon ships a component that generates list items for recent bug reports on a project. Then let's say that you decide to use ember-font-awesome to decorate each item with a bug icon.

You're building out a demo inside of the dummy app directory, and everything looks great. But then you go to test the addon inside of another application and HTMLBars throws something like this:

A helper named 'fa-icon' could not be found  

What's being witnessed is that because we're no longer inside of our addon project, Ember's resolver is not finding the fa-icon helper that ember-font-awesome was kindly providing before. To solve this, we need to jump into the afterInstall hook of our addon's default blueprint and link the consuming application with our addon's addon by using the addPackageToProject function:

module.exports = {  
  normalizeEntityName: function() {},

  afterInstall: function() {
     return this.addPackageToProject('{{PACKAGE_NAME}}', '{{VERSION}}');
  }
};

* Remember to return from this operation, as Ember CLI will be expecting to resolve a promise from these hooks when running the blueprint.

Another way to comprehend just what this entire process is all about: The code that’s inside of your addon's /app directory is seen by the parent as its own code during compile time. The addPackageToProject process makes it so that that where your addon’s code is using ember-font-awesome, the application has ember-font-awesome as its own dependency — because that's how the application code is going to be expecting it.

* When not to do this

Given how powerful this technique can be, however, it’s equally important to know when it’s completely unnecessary. I made the mistake of using it for some PostCSS dependencies in a recent add-on — even though those dependencies were only being used internally by the add-on. Fortunately, that’s when @rwjblue appeared ex machina in my issue tracker with a wonderfully clear explanation of where I went wrong:

In short, a good mental model to use is the fact your addon already is using other addons -- whether it's ember-cli-qunit or ember-disable-prototype-extensions -- with no such manual alley-ooping required. If your consuming application isn't touching the specific package at runtime, you should be in the clear.

Configurability Counts

Sometimes, an operation that your addon is performing will benefit greatly from knowledge of a user preference (e.g: here's my hostname and urlPrefix for making requests) or knowledge of the current runtime environment (e.g: call foo() with bar in testing, but baz in production).

Naturally, there are many ways that this can be achieved. Configuring ember-cli-mirage, for example, can be a large part of configuring your entire application as a whole, and so Mirage will stake out an entire folder structure at the root of your app to set it up. Most addons will have a much smaller scope, however, and two common patterns have emerged for accepting lightweight configs.

One involves allowing users to provide a config as a build option inside of their .ember-cli-build.js file:

module.exports = function(defaults) {  
  const app = new EmberApp(defaults, {

    {{YOUR_ADDON_NAME}}: {
      option1: "foo"
    },
    ...
  });

  ...
  return app.toTree();
};

From there, your addon can read these options in the included hook of its index.js file:

module.exports = {  
  name: '{{YOUR_ADDON_NAME}}',

  included(app) {
    this._super.included.apply(this, arguments);
    const addonBuildOpts = app.options.{{YOUR_ADDON_NAME}};

    //
    // do something with opts
    //     
  }
};

The second pattern utilizes an application's environment configuration and is great for enabling different settings for different runtime states.

It looks a lot like the previous approach, but this time, we're inside the application's config/environment.js file:

module.exports = function(environment) {

  const ENV = {
    modulePrefix: 'fooApp',
    environment: environment,
    ...
    ...   
  };

  if (environment === 'development') {
    ENV['{{YOUR_ADDON_NAME}}'] = {
      opt1: 'foo'
    };
  }

  if (environment === 'test') {
    ENV['{{YOUR_ADDON_NAME}}'] = {
      opt1: 'bar'
    };
  }
  ...
};

The only difference now is how we retrieve these settings inside of our addon.

Perhaps we're doing so in code that's destined to become application code -- for example, a component or a service. This is code that, at runtime, will have an application container and can be looked up by Ember's resolver. With that in mind, we can use said application container to lookup the app's configuration:

import Ember from 'ember';

const { getOwner } = Ember;

export default Ember.Mixin.extend({  
  init() {
    this._super(...arguments);

    const appConfig = getOwner(this).lookup('config:environment');
    const addonConfig = appConfig.{{YOUR_ADDON_NAME}};

    //
    // do something with addon configuration options
    //
  }
});

If we want to, though, we can still retrieve the addon's config inside the included hook of its index.js -- right alongside the build options from our previous example:

module.exports = {  
  name: '{{YOUR_ADDON_NAME}}',

  included(app) {
    this._super.included.apply(this, arguments);

    const addonBuildConfig = app.options.{{YOUR_ADDON_NAME}};
    const addonENVConfig = app.project.config(app.env)['{{YOUR_ADDON_NAME}}'] || {};

    //
    // do something with addon build opts
    // 

    //
    // do something with addon env opts
    // 
  }
};

Configurability is a Two-Way Street

Not only can applications communicate their desired addon configurations to addons, addons can communicate their desired application configuration to applications. The config hook of index.js can return an object that gets merged with the application's configuration object (Important to note, though, is that the application config always takes precedence), and while you could, hypothetically, attempt this in any hook that exposes the app instance, config is special: It's called with the application's current runtime environment -- a caveat of context that comes in handy when... you know... setting the environment configuration.

Here's a brief example of how an addon can use config to modify features of its consuming application:

module.exports = {  
  name: 'ember-isolationism',

  config: function(env, config) {
    return {
      EmberENV: {
        FEATURES: {
          I18N_SUPPORTED_LANGUAGES: []
        }
      }
    };
  }
};

The Art of NPM Linking

If you haven't use NPM linking before, it's a great way to simulate how your package will be consumed and used by another project before exposing it to the public NPM registry. Seriously, give it a shot; you'll feel like you've discovered a dark art.

But Ember CLI takes this a step further for addons.

We can reap the benefits of NPM links and live reloading by returning true in the isDevelopingAddon hook of the addon's index.js:

isDevelopingAddon: function() {  
  return true;
}

From there, we just need to perform a one-time wiring:

  • npm link in the addon directory
  • npm link {{ADDON_NAME}} in your consuming Ember project.
  • Specifying {{ADDON_NAME}}: * in the devDependencies of in your consuming Ember project.
  • Running ember g {{ADDON_NAME}} in your consuming Ember project to kick off the default blueprint.

Just remember to have isDevelopingAddon return false (or remove the function entirely) before ship time 👍.

Dummy Apps are Smarter than You Know

Matryoshka dolls teach us to expect a bit less from each of our descendants, so naturally I was surprised to learn that this isn't so for addon dummy apps. Nestled in a dummy folder within an addon's test directory, these are fully functional, ember serveable Ember applications that Ember CLI effortlessly stubs out for us when we generate a project.

And oh, the things we can use them for: From acceptance-testing a project that puts our addon to use to serving as a statically hosted showcase of the shiny new tool we made, integrating a dummy app into our workflow addon brings a whole new dimension of sophistication to our development process.

Regarding the former, Ember Screencasts has a great tutorial on Ember Addon acceptance testing -- but here's a hint:

ember g acceptance-test {{test-name}} --dummy  

It's that seamless.

Which can't quite be said for checking out an orphaned gh-pages branch and generating a production build of the dummy app so that it can be sent up to the root of your project via an index.html file with minified and newly revisioned assets being accurately sourced from its <head>. But no worries. Encapsulating this in a few quick steps is precisely the reason ember-cli-github-pages was created. Put it to use the next time you update your API documentation or create a new sample for the super-cool Service Worker utility that you've been polishing, and you'll have them on the public stage in a matter of seconds.

Versioning & Publishing

As mentioned, Ember addons are NPM Packages, so it's critical to have a versioning strategy for fixing bugs or developing new features, and rolling out those changes to your users. This is the part where most guides on Node development would say "just use SemVer", and call it a day with a spec link. But this is Ember; we should have an addon to make it seamless.

Enter Ember CLI Release.

This addon adds the ember release command to your project and -- by assuming that you're already using SemVer -- automatically bumps your project version (see configuration options for specifying the magnitude of the bump) in package.json, handles any out-standing commits, and pushes the proper project changes and Git tags to your repository.

Even if you decide not to use ember-release, however, please don't try to keep the Order of Operations for versioning in your head. I'm convinced it's not possible. Much easier would be getting this snippet as a tattoo:

npm version {{NEW_VERSION}}  
git push origin master  
git push origin --tags  
npm publish  
Tags: ember-cli, Addons, ergonomics