Skip to content

Instantly share code, notes, and snippets.

@james-jlo-long
Last active May 25, 2018 13:01
Show Gist options
  • Save james-jlo-long/f463c43a1fe5058520fe939d52b75942 to your computer and use it in GitHub Desktop.
Save james-jlo-long/f463c43a1fe5058520fe939d52b75942 to your computer and use it in GitHub Desktop.
A poor-man's require. A simple JavaScript module manager
var myNamespace = (function () {
"use strict";
/**
* A collection of all defined modules.
* @private
* @type {Object}
*/
var defined = Object.create(null);
var empty; // Created once Definition is created.
/**
* Gets the resolved dependency.
*
* @private
* @param {String} name
* Name of the dependency to get.
* @return {Promise}
* Promise that resolves with the dependency value.
*/
function getResolved(name) {
var def = defined[name] || empty;
return def.resolve();
}
/**
* A module definition.
*
* @constructor
* @private
* @param {Array.<String>} dependencies
* Name of the dependencies for this module.
* @param {Function} factory
* Function that creates the module.
*/
function Definition(dependencies, factory) {
/**
* The names of the dependencies for this module.
* @type {Array.<String>}
*/
this.dependencies = dependencies;
/**
* The factory for creating this module.
* @type {Function}
*/
this.factory = factory;
}
Definition.prototype = {
/**
* Creates the promise that resolves with the return value of
* {@link Definition#factory}. All {@link Definition} instances that
* match the names in {@link Definition#dependencies} are passed to the
* factory in the order in which they were requested.
*
* @return {Promise}
* Promise that resolves with the module value.
*/
resolve: function () {
var that = this;
if (!that.promise) {
/**
* Promise that resolves with the module definition.
*
* @type {Promise}
*/
that.promise = Promise
.all(that.resolveDependencies())
.then(function (values) {
return that.factory.apply(undefined, values);
});
}
return that.promise;
},
/**
* Converts {@link Definition#dependencies} into resolved module
* promises.
*
* @return {Array.<Promise>}
* Resolved module promises.
*/
resolveDependencies: function () {
return this.dependencies.map(getResolved);
}
};
/**
* A definition that resolves to nothing. Used as a placeholder for any
* module that isn't found.
*
* @private
* @type {Definition}
*/
empty = new Definition([], function () {
return;
});
return {
/**
* Create a module definition.
*
* @memberof myNamespace
* @param {String} name
* Name of the module to create.
* @param {Array.<String>} [dependencies]
* Optional dependencies for the module.
* @param {Function} factory
* Factory that creates the module.
*/
define: function (name, dependencies, factory) {
if (factory === undefined) {
factory = dependencies;
dependencies = [];
}
defined[name] = new Definition(dependencies, factory);
},
/**
* Gets a Promise that resolves the with the module's value. If a
* handler is passed in, it is executed when the module is resolved but
* it does not modify the module's value.
*
* @memberof myNamespace
* @param {Array.<String>} names
* Name of the module to retrieve.
* @param {Function} [handler]
* Optional handler to execute when the module resolves.
* @return {Promise}
* Promise that resolves with the module's value.
*/
require: function (names, handler) {
var modules = Promise.all(names.map(getResolved));
if (typeof handler === "function") {
modules.then(function (dependencies) {
handler.apply(undefined, dependencies);
});
}
return modules;
},
/**
* Gets an array of all the defined module names. Pass them to
* {@link myNamespace.require} to get the module value(s).
*
* @memberof myNamespace
* @return {Array.<String>}
* Array of all the modules that have been defined.
*/
getDefined: function () {
return Object.keys(defined);
}
};
}());
// Creating a module
//
// Here's a simple module that is created. The function isn't executed until
// another module tries to use it and it's only executed once.
myNamespace.define("one", function () {
return 1;
});
// Here's a module that requires another module. This doesn't execute the
// function, nor does it execute the dependency's function. When the dependency
// is resolved, its value will be passed to this factory.
myNamespace.define("two", ["one"], function (one) {
return 1 + one;
});
// Retrieving a module
//
// Here is a module getting accessed. This executes the factory function for
// "two" (which executes the factory function for "one").
myNamespace.require(["two"], function (two) {
console.log(two);
});
// Logs: 2
// The function returns a Promise, so you can use `.then()` to add more
// callbacks. `.then()` gets an array of module values instead of the individual
// values like the callback.
myNamespace.require(["two"]).then(function (values) {
console.log(values);
});
// Logs: [2]
// The callback function does not modify the module value.
myNamespace.require(["two"], function (two) {
return two * 2;
}).then(function (values) {
console.log(values);
});
// Logs: [2]
// Replacing a module
//
// This can be done at any time.
myNamespace.require(["two"], function (two) {
console.log(two);
});
// Logs: 2
myNamespace.define("two", ["one"], function (one) {
return 2 + one;
});
myNamespace.require(["two"], function (two) {
console.log(two);
});
// Logs: 3
// Undefined modules
//
// If a module isn't found, `undefined` is passed to the factory. No error is
// thrown.
myNamespace.require(["does-not-exist"], function (thing) {
console.log(thing);
});
// Logs: undefined
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment