Skip to content

Instantly share code, notes, and snippets.

@gnap
Last active December 15, 2015 14:39
Show Gist options
  • Save gnap/5275855 to your computer and use it in GitHub Desktop.
Save gnap/5275855 to your computer and use it in GitHub Desktop.
A module loader from Quora.
this.require || function (global, undefined) {
// The `require` function takes an absolute module identifier and returns
// the `exports` object defined by that module.
function require(moduleId) {
if (!installed[moduleId]) {
log("loader", "required", moduleId);
var exports, module = defines[moduleId];
if (module) {
module.id = moduleId, module.exports = exports = {};
module(function (id) {
return subRequire(id, moduleId);
},
installed[moduleId] = exports,
module);
}
if (module.exports && module.exports !== exports) {
installed[moduleId] = module.exports;
log("loader", "exported", moduleId);
}
}
return installed[moduleId];
}
function subRequire(id, baseId) {
return require(id = absolutize(id, baseId)) || die(id);
}
function die(msg) {
throw msg;
}
// Given two module identifiers `id` and `baseId`, the `absolutize`
// function returns the absolute form of `id`, as if `id` were required
// from a module with the identifier `baseId`. For more information about
// relative identifiers, refer to the
// [spec](http://wiki.commonjs.org/wiki/Modules/1.1#Module_Identifiers).
var pathNormExp = /\/(\.?|[^\/]+\/\.\.)\//;
function absolutize(id, baseId) {
if (baseId && /^\./.test(id)) {
id = "/" + baseId + "/../" + id;
while (id != (baseId = id.replace(pathNormExp, "/"))) {
id = baseId;
}
}
return id.replace(/^\//, "");
}
function resolve(module) {
if (!module.unmet) {
var code = module + "", match;
var unmet = module.unmet = {};
requireExp.lastIndex = 0;
while ((match = requireExp.exec(code))) {
unmet[match[1]] = 1;
}
}
return module.unmet;
}
// A module is `ready` to be evaluated if
//
// 1. it is an object;
// 2. it is in the installed module table;
// 3. all of its direct dependencies are defined and `ready` to be evaluated.
//
function ready(module) {
if (typeof module == "object") return !0;
var hasMissing, found = {}, deps = resolve(module);
var g;
for (g in deps) {
if (installed[g] || defines[g])
found[g] = 1;
else
hasMissing = 1;
}
// Once a dependency is determined to be satisfied, we
// remove its identifier from `deps`, so that we
// can avoid considering it again if `ready` is called
// multiple times.
for (g in found) {
delete deps[g];
}
if (hasMissing) {
printError("missing modules:");
for (g in found) printError(g);
}
// If any dependency is missing or not `ready`, then the
// current module is not yet `ready`.
return !hasMissing;
}
function printError() {
try {
var console = global.console;
if (console)
console.log.apply(console, arguments);
} catch (c) {}
}
// The `flushQueue` function attempts to evaluate the oldest module in the
// queue, provided all of its dependencies have been installed. This
// provision is important because it ensures that the module can call
// `require` without fear of missing dependencies.
function flushQueue() {
if (!flushing) try {
while (qhead !== qtail && ready(qhead.next)) {
flushing = qhead;
qhead = qhead.next;
delete flushing.next;
log("loader", "flushQueue", "start");
qhead(subRequire);
log("loader", "flushQueue", "finish");
}
} finally {
flushing = 0;
}
}
function log() {
return recorded[recorded.length] = {
time: +(new Date()),
tags: recorded.slice.call(arguments)
};
}
// The `installed` object maps absolute module identifiers to module
// exports available for requirement.
var installed = {};
// The `defines` object maps absolute module identifiers to functions,
// functions are executed only once when the first requirement.
var defines = {};
var recorded = [];
// Anonymous modules are pushed onto a queue so that (when ready) they can
// be executed in order of installation.
var qhead = {}, qtail = qhead;
// If `install` is called during the evaluation of a queued module,
// `flushQueue` could be invoked recursively. To prevent double evaluation,
// `flushQueue` sets `flushing` to a truthy value before it evaluates a
// module and refuses to evaluate any modules if `flushing` is truthy
// already.
var flushing;
// To be recognized as dependencies, calls to `require` must use string
// literal identifiers.
var requireExp = /require\(['"]([^'"]+)['"]\)/g;
// To `install` a named module, pass an absolute module identifier
// string followed by a module definition. Note that named modules are
require.install = function (moduleId, module) {
log("loader", "installed", moduleId);
var store = typeof module == "object" ? installed : defines;
if (!store[moduleId]) {
store[moduleId] = module;
flushQueue();
}
};
// Enqueue an anonymous module. Anonymous modules are executed in order of
require.enqueue = function (module) {
qtail = qtail.next = module;
if (qhead.next === qtail) {
if (ready(module))
flushQueue();
}
};
log.dump = function () {
return recorded.slice();
};
global.rec = log;
global.require = require;
log("loader", "ready");
}(this);;
// tests
this.require.install('ac', {bb:1});
this.require.install('bc', {cc:1});
this.require.enqueue(function (require) {
console.log('seq 1');
var ac = require('ac');
console.log('seq 1', ac);
});
this.require.enqueue(function (require) {
console.log('seq 2');
var ac = require('ac');
var bc = require('bc');
console.log('seq 2', bc);
});
console.log(this.rec.dump());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment