Skip to content

Instantly share code, notes, and snippets.

@kixxauth
Created November 21, 2010 20:02
Show Gist options
  • Save kixxauth/709088 to your computer and use it in GitHub Desktop.
Save kixxauth/709088 to your computer and use it in GitHub Desktop.
Enterprice Resource Management application with CommonJS 2.0 JS loader.
// ========================
// CommonJS 2.0 environment
// ========================
//
// (unratified draft specification)
// --------------------------------
var env = (function (GLOBAL, undefined) {
'use strict';
// memoized exports objects
var require_memo = {}
// memoized factory functions
, factory_memo = {}
// memoized module objects
, module_memo = {}
// the currently loading module id
, currently_loading
// memoized module resources (as functions)
, resource_cache = {}
// just a shortcut
, hasown = Object.prototype.hasOwnProperty
// constructors defined later
, require
, module
;
// custom error constructor
function requireError(self) {
self.name = 'RequireError';
return self;
}
// cache a resource function
function resource(path, fn) {
resource_cache[path] = fn;
}
// dereference an exports object from the memo and return it
function get(id) {
if (hasown.call(require_memo, id)) {
return require_memo[id];
}
throw requireError(
new Error('The module "'+ id +'" could not be found.'));
}
// create and memoize a new exports object with the given id
function set(id) {
if (!hasown.call(require_memo, id)) {
return (require_memo[id] = {});
}
throw requireError(
new Error('The module "'+ id +'" already exists.'));
}
// determine if a module factory has already been loaded
function loaded(id) {
return hasown.call(factory_memo, id);
}
// invoke a module resource
function load(path) {
currently_loading = path;
resource_cache[path](GLOBAL);
}
// determine the currently loading module id
function loading() {
return currently_loading;
}
// get or set a module factory function
function factories(key, fn) {
if (fn) {
factory_memo[key] = fn;
return;
}
return factory_memo[key];
}
// determine if a factory function has already been invoked
function invoked(id) {
return hasown.call(module_memo, id);
}
// invoke a factory function with the given id and exports object
function doinvoke(id, exports) {
var require = requireFactory(id)
, mod = (module_memo[id] = module({id:id, require: require}))
;
factories(id)(require, exports, mod);
}
// return a resolved id given the current location and a relative id
function resolve(current, id) {
// A module id that does not begin with a '.' is an absolute
// identifier, so we just return it.
if (id.charAt(0) !== '.') {
return id;
}
var resolved = current.split('/')
, parts = id.split('/')
, i
, part
;
// Pop off the module name of the current location.
resolved.pop();
for (i = 0; i < parts.length; i += 1) {
part = parts[i];
if (part.charAt(0) !== '.') {
resolved.push(part);
}
else if (part === '..') {
resolved.pop();
}
}
return resolved.join('/');
}
// constructor
module = function Module(spec) {
if (!(this instanceof Module)) {
return new Module(spec);
}
this.id = spec.id;
this.require = spec.require;
};
// declare / define a module and dependencies
Module.prototype.declare = function (deps, factory) {
if (typeof deps === 'function') {
factory = deps;
deps = [];
}
var id = loading();
this.require.memoize(id);
if (deps.length) {
this.provide(deps, function () {
factories(id, factory);
});
return;
}
factories(id, factory);
};
// provide module dependencies to the environment
Module.prototype.provide = function (deps, callback) {
var resolver = this.require.id
, i
, id
;
for (i = 0; i < deps.length; i += 1) {
id = resolver(deps[i]);
if (!loaded(id)) {
load(id);
}
}
callback();
};
// not implemented
Module.prototype.load = null;
// contructor
// the 'extra module environment' should specify `extra_require`
requireFactory = function Require(loc, extra_require) {
var self;
// the actual `require` function
function new_require(id) {
id = self.id(id);
var module_exports = get(id);
if (!invoked(id)) {
doinvoke(id, module_exports);
}
return module_exports;
}
// use a different require function if specified
self = typeof extra_require === 'function' ?
extra_require : new_require;
// resolve an id
self.id = function (id) {
return resolve(loc, id);
};
// creat and memoize and exports object with the given id
self.memoize = set;
return self;
};
// special require function for the 'extra module environment'
function extra_require(id) {
if (id.charAt(0) !== '.') {
var module_exports = get(id);
doinvoke(id, module_exports);
return module_exports;
}
throw requireError(new Error( 'Relative module identifiers are not '+
'available in the extra module '+
'environment: '+ id));
}
// export the globals
GLOBAL.require = requireFactory('', extra_require);
GLOBAL.module = module({require: GLOBAL.require});
// special module.declare function used to bootstrap the main module
GLOBAL.module.declare = function bootstrap(deps, factory) {
var id = '';
// remove the declare instance method
delete this.declare; // the prototype.declare method remains
// define the resource containing the main module
resource(id, function () { this.declare(deps, factory); });
load(id);
this.require(id);
};
// return the `env` namespace
return {resource: resource};
}(window));
// =============================
// CommonJS 2.0 compient modules
// =============================
// ----------------------------------------------------------------------------
env.resource('thrower', function (window) {
var sto = window.setTimeout;
/**
* @module thrower
* @exports {Function} thrower
*/
module.declare(function (require, exports, module) {
'use strict';
/**
* Delay throwing an error by putting it on top of the execution stack.
* @param {Object} e Something to throw; usually an `Error` object.
*/
exports.thrower = function thrower(e) {
sto(function () { throw e; }, 0);
};
}); });
// ----------------------------------------------------------------------------
env.resource('broadcaster', function (window) {
var deps = ['thrower'];
/**
* @module broadcaster
* @exports {Function} broadcaster
*/
module.declare(deps, function (require, exports, module) {
'use strict';
var thrower = require('thrower').thrower;
/**
* Broadcast a set of arguments to a list of functions.
* @param {Array} callbacks A list of functions to broadcast to.
* @param {Array} args A list of arguments to apply to each callback.
* @param {Object} [context] Will become `this` inside callback functions.
*/
exports.broadcast = function broadcastor(callbacks, args, context) {
var i = 0, len = callbacks.length;
context = context || {};
for (; i < len; i += 1) {
try {
callbacks[i].apply(context, args);
} catch (e) {
// Report a callback error after it can no longer get in our way.
thrower(e);
}
}
};
}); });
// ----------------------------------------------------------------------------
env.resource('namespaced-emitter', function (window) {
var deps = ['broadcaster'];
/**
* @module
*/
module.declare(deps, function (require, exports, module) {
'use strict';
var broadcast = require('broadcaster').broadcast
, hasown = Object.prototype.hasOwnProperty
;
/**
* Bind emitter methods to an object.
* @param {Object} [self] The object to bind to. Defaults to `{}`.
* @returns {Object} The same object passed in; or `{}`.
*
* The `.emit()` and `.on()` methods are bound to the object.
*/
exports.emitter = function emitter(self) {
self = self || {};
var registry = [];
/**
* Emit event data to registered handlers.
* @param {String} path Namespaced event name.
* @param {Array} data Array of arguments to pass to handlers.
*/
self.emit = function emit(path, data) {
while (path) {
if (hasown.call(registry, path)) {
broadcast(registry[path], data);
}
path = path.slice(0, path.lastIndexOf('.'));
}
};
/**
* Bind an event handler to this emitter object.
* @param {String} path Namespaced event name.
* @param {Function} fn The callback function to call.
* This will most likely be your public API.
*/
self.on = function on(path, fn) {
if (!hasown.call(registry, path)) {
registry[path] = [];
}
registry[path].push(fn);
};
return self;
};
}); });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment