Writing an Ember add-on that wraps an NPM module

Most of the time, packages that you need to use in your Ember app are written with the client-side in mind, or at the very least, have versions for both server-side and client-side.

However, you may come across a package that it is implemented in Node only (server-side).

Browserify is handy for the use-case of importing NPM modules into your browser-run code. With Ember, you could use ember-browserify to make the package available in your app code.

But you may want to implement Ember features by extending the package. And you may also want to wrap it as an add-on so that others may benefit from the work you put into adapting it for Ember.

This is what happened to me recently and detailed below are the steps I went through to accomplish this.

The package: normalize-url

Normalize-url will take a string parameter, of any url, and spit out a normalized version of it. For example, if I were to give it twitter.com, it would give me http://twitter.com.

We needed this to normalize user input in our Ember app. Unfortunately, it was only written in Node.js. Making it compatible for the front end meant using ember-browserify, the Ember add-on for Browserify.

ember-browserify

Simply put, using ember-browserify lets you use your NPM dependencies from inside of your app.

If I wanted to use normalize-url inside of my Ember app, I would first install ember-browserify followed by the npm package I'd want to use:

$ ember install ember-browserify
$ npm install -S normalize-url

Then in my app, I would do:

import normalizeUrl from 'npm:normalizeUrl';  

And now I can use normalizeUrl to normalize any URLs in my application code.

This certainly gave me a working solution, but I wanted to use normalize-url in my templates, via Handlebars helper. I also wanted to avoid prepending of 'npm:' to all my import statements.

Wrapping the whole thing in an add-on was the next logical step.

Creating ember-normalize-url

First, I instantiated my add-on directory:

$ ember addon ember-normalize-url

Next, I took care of dependencies. I needed this add-on to install both ember-browserify and normalize-url on the host app's dependencies.

(This was partly determined by the instructions provided on the ember-browserify README.)

The default blueprint of your add-on is the place to install packages onto your host app.

The default blueprint uses the same name as your add-on, and is run automatically when your add-on is installed. Using ember-cli, I generated my default blueprint:

$ ember g blueprint ember-normalize-url

Inside the blueprint, I returned two promises in the afterInstall method which will take care of installing the two packages onto the host app:

var RSVP = require('rsvp');

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

  afterInstall: function() {
    return RSVP.all([
      this.addPackageToProject('normalize-url'),
      this.addAddonToProject('ember-browserify'),
    ]);
  }
};

(normalizeEntityName no-op is needed when developing add-ons; I did experience issues without it, haven't figured out why)

The name of the add-on is ember-normalize-url but I would like users of the add-on to be able to include it by just the name normalize-url. So I made the appropriate changes to root-level index.js.

module.exports = {  
  name: 'normalize-url'
};

export default normalizeUrl;

Now for the meat and potatoes: app/index.js and addon/index.js.

Normalize-url exports a simple function in its natural habitat, so I would like it to work in a similar way in my Ember app, for example:

import normalizeUrl from 'normalize-url';

normalizeUrl('twitter.com'); // === http://twitter.com  

So if we want normalizeUrl to act as a function, we need to export a function from addon/index.js.

// addon/index.js
import normalizeUrl from 'npm:normalize-url';

export default normalizeUrl;  

Due to a requirement from ember-browserify, we would have to do the same in app/index.js (rather than import the corresponding addon/index.js file that normally would be done in an Ember add-on).

// app/index.js
import normalizeUrl from 'npm:normalize-url';

export default normalizeUrl;  

{{normalize-url}} helper

To create my template helper, I used ember-cli to scaffold out the needed files:

$ ember g helper normalize-url

The code was pretty straightforward:

import Ember from 'ember';  
import _normalizeUrl from 'normalize-url';

export function normalizeUrl(params, options) {  
  return params.length && params[0] ? _normalizeUrl(...params, options) : '';
}

export default Ember.Helper.helper(normalizeUrl);  

Just a basic Handlebars helper. Notice that I can import my own add-on in this context as shown in the 2nd line.

After some rudimentary param checking, the method will return either the result of _normalizeUrl or an empty string.

Publishing

Following this handy gist that has helped many before me and will surely help many after, I executed the following commands (after committing my changes) to publish this add-on to NPM, emberaddons.com and to the 🌍.

$ npm version 0.0.1
$ git push origin master
$ git push origin --tags
$ npm publish