Node + Ember

Dean Sofer

whois dean sofer?

Wise Man Says

  • API design === User Experience Design
  • Refactor-friendly FTW
  • Composition over Convention
  • Obfuscation for its own sake is evil
  • Pretend everything is public
  • Expect the unexpected

Node First

  • The library is node first, everything else second
  • Node packages and standards increase compatibility
  • Ember has a very specific and opinionated API
  • Core library is written without knowledge of Ember

ES6 in Node.js

  • Native ES6 support with 'use strict'
  • Easier development / debugging
  • See node.green for details
  • Compiled source tree for most
  • BUT DON'T BUNDLE NODE CODE!

Bundling Antipattern

Redundant bundling breaks tree-shaking

  • Redundant libraries
  • Bigger filesize
  • Debugging is much harder
  • Complicated dependency upgrades
App
- Dependency B
- Library(A+B)
App(B+Library(A+B))
Library
- Dependency A
- Dependency B
Library(A+B)

bundle

library

bundle

app

Compiled Tree

/*** core-library/package.json ***/
{
  ...
  "main": "compiled/index.js",
  "dependencies": {
    "babel-preset-es2015": "^6.6.0",
    "babel": "^6.5.2"
  }
}
/*** core-library/.babelrc ***/

{
  "presets": [
    "es2015"
  ]
}

Save the trees!

  • Files are local, unlike websites
  • Files are smaller
  • Building is faster (and incremental)

Node in Ember

  • Ember recommended solution
  • Bower is dying. Long live Browserify!
  • Namespaced imports "npm:core-library"
  • Browserify can compile too

?

Compiling Antipattern

DON'T double compile your code

es6

compile

compile

watchers

sourcemaps

es5

bundled

waiting

watchers

waiting

sourcemaps

build tool A

build tool B

Targetting Clients

/*** core-library/package.json ***/
{
  ...
  "browser": "src/index.js",
  "browserify": {
    "transform": [
      "babelify"
    ]
  },
  "dependencies": {
    ...
    "babel-preset-es2015": "^6.6.0",
    "babelify": "^7.2.0"
  }
}
/*** core-library/.babelrc ***/

{
  "presets": [
    "es2015"
  ]
}

es6

es5 bundled

browserify

Separating Concerns

  • What can I do without Ember?
    • Classes
    • Queries
    • Reads
    • Utilities
  • What must I do with Ember?
    • Rendering
    • Ember.set()
    • Function.property()

Show me

the Code

/*** core-library/Base.js ***/
class Base {
  constructor(data) {
    Object.assign(this, data);
  }
}
module.exports = Base;

/*** core-library/Project.js ***/
const Base = require('./Base');
const Task = require('./Task');

class Project extends Base {
  static get(id) {
    return query(`/projects/${id}`).then( project =>
      new Project( project );
    );
  }
  constructor(data) {
    super(data);
    this.tasks = this.tasks.map( task => 
      new Task( task )
    );
  }
  isComplete() {
    return this.tasks.every( task => 
      task.completed
    );
  }
}
module.exports = Project;

But Ember-Data!

  • Ember-Data doesn't work for everything
  • Want to use business logic outside of Ember
  • We have a terrible server API
  • Easy to roll our own with ES6
  • Code is transparent and refactor friendly

ember Wrapper

/*** my-addon ***/

import CoreLibrary from 'npm:core-library';
import Ember from 'ember';

const Base = CoreLibrary.Base;

// Monkey-patch CoreLibrary with Ember utils
Base.prototype.set = function(property, value) {
    return Ember.set(this, property, value);
};
Base.prototype.get = function(property) {
    return Ember.get(this, property);
};

// Configure environment for Ember statically
CoreLibrary.setPromiseLibrary(Ember.RSVP.Promise);
CoreLibrary.configure({ ... });

// Or create Ember configured instance
export default new CoreLibrary({ ... });
  • Ember addon for core library
  • Handles installation
  • Handles dependencies
  • Handles library configuration
  • Adds Ember-specific logic
  • NOT an alias for core library

Ember Wrapper Setup

/*** my-addon/package.json ***/
{
  ...
  "peerDependencies": {
    "ember-browserify": "^1.1.8",
    "my-node-library": "^1.0.0"
  }
}


/*** my-addon/blueprints/my-addon/index.js ***/
module.exports = {
  ...
  afterInstall: function(options) {
    return this.addPackagesToProject([
      { name: 'ember-browserify' },
      { name: 'my-node-library' }
    ]);
  }
};
my-app/
  node_modules/
    ember-browserify/

    my-addon/
      addon/
        index.js

      app/
        dependencies.js

      blueprints/
        my-addon/
          index.js

    core-library/
      library.js
      package.json

      node_modules/
        babelify/
        babel-preset-es2015/
/*** my-addon/addon/index.js ***/

import CoreLibrary from 'npm:core-library';

...



/*** my-addon/app/dependencies.js ***/

import coreLibrary from 'npm:core-library';

// Informs ember-cli of npm:core-library
// This file never gets executed

Ready to Use

/*** my-app ***/

import CoreLibrary from 'npm:core-library';
import coreLibrary from 'my-addon';

CoreLibrary.Project.get(123).then( project =>
  project.set('name', 'Presentation');
);

coreLibrary.Project.get(123).then( ... )
ember install my-addon

mind = blown

FEEN

Node + Ember

By Dean Sofer