CommonJS modules in apps

When authoring an app, you may find that it's more maintainable if you break discrete functionality into modules that can be loaded as required. Each module is a single JavaScript file.

The Zendesk Apps framework provides a module-loading system based on the CommonJS module system popularized in Node.js, and now commonly used in the browser too. See Modules in the Node.js API docs.

When creating modules for your app, put the JavaScript file for each module in a folder called lib located in the root of your app's folder structure. You can also put module files in subfolders of lib to further organize your code.

To load a module from the lib folder, use the require function available in an app's global scope. This is similar to the way in which you access services or helpers in an app. Indeed, services and helpers are also available within the global scope of module code. In this sense, modules share the global scope of an app.

Using require

The signature of the require Function could be described using type annotations like:

require(modulePath:string):any

modulePath examples:

require('...') loads
Foo /lib/Foo.js
foo.js /lib/foo.js
bar/quux /lib/bar/quux.js
./sub/bar relative path to sub/bar.js
../foo/bar relative path to foo/bar.js

The module name is sufficient; the .js file extension is optional.

Example

The CommonJS sample app in our demo apps on Github uses modules.

In the following example, the app.created event in the app.js file loads a module called Foo:

events: {
  'app.created': function() {
    var Foo = require('Foo');

    var foo = foo.getFoo();
    console.log(foo); //1;

    var foobar = foo.getFoo();
    console.log(foobar); //3;
  }
}

Contents of lib/Foo.js

var foo = 1,
    bar = 2;

module.exports = {
  getFoo: function() {
    return foo;
  },
  getFoobar: function() {
    return this.getFoo() + bar;
  }
}

Pitfalls

One thing to be aware of when using modules is that module code is shared across all instances of the app. This is no problem for top_bar or nav_bar apps, but likely to be an issue for sidebar apps.

Additionally, the this variable will be the module, not the app, unless the function got bound to the right context.

To clarify: lib/FooBar.js

var foo = 1;

module.exports = {
  getFoo: function() {
    return foo;
  },
  setFoo: function(val) {
    foo = val;
    return foo;
  },
  getBar: function() {
    return this.bar;
  },
  setBar: function(val) {
    this.bar = val;
    return this.bar;
  }
}

The variable foo is shared by all instances. this.bar is shared, unless you set the context (bind/apply/call), or extend the main object.

Access to the app

In app.js, you normally have access to this, which represents your app. Examples: this.switchTo or this.customFields. In your module, the this keyword represents your module, not your app. There are different solutions for getting access to your app, a few of which are presented here for clarity.

  1. Passing it as an argument
  2. Extending the app
  3. Binding the module functions
  4. Creating an instance
Passing it as an argument

The most straightforward way is simply passing the this as an argument to your module's function. The downside is that it can be confusing what the this keyword represents and moving code around will become tricky because the interface (of the function) is directly related to the implementation.

app.js

(function() {
  return {
    events: {
      'app.created': 'init'
    }

    init: function() {
      var foo = require('foo');
      foo.doBar(this);
    }
  };
}());

lib/foo.js

module.exports = {
  doBar: function(app) {
    app.switchTo('bar');
  }
};
Extending the app

Extending the main app object is one way, in app.js, your module will be part of the main object.

(function() {
  return _.extend({}, require('Foo'), require('Parser'), {
    myFunction: function() {
      this.field1234(); // returns the value of custom field 1234
      this.parser(); // switches the template.
    }
  });
}());

And respectively lib/Foo.js

module.exports = {
  field1234: function() {
    return this.ticket().customField('custom_field_1234');
  }
}

and lib/Parser.js

module.exports = {
  parser: function(data) {
    this.switchTo('myTemplate', data);
  }
}
Binding the module functions

Another solution that can be helpful is using bind (or call/apply) to set the context of the function when you call it.

(function() {
  var Field = require('Field');

  return {
    myFunction: function() {
      Field.getValue.call(this, 1234);
    }
  }
}());

And respectively lib/Fields.js

module.exports = {
  getValue: function(id) {
    return this.ticket().customField( helpers.fmt('custom_field_%@', id) );
  }
}
Creating an instance

A fourth solution is to create an instance using JavaScript's excellent object oriented model -- either using Object.create or constructor functions and the new keyword.

app.js

(function() {
  return {
    events: {
      'app.created': function() {
        this.Foo = new require('Foo')(this);
        this.Foo.getValue(1234);
      }
    }
  }
}());

And respectively lib/Foo.js

function Foo(app) {
  this.app = app;
}
Foo.prototype = {
  getValue: function(id) {
    return this.app.ticket().customField( helpers.fmt('custom_field_%@', id) );
  }
};

module.exports = Foo;