Last active
December 15, 2015 14:39
-
-
Save gnap/5275855 to your computer and use it in GitHub Desktop.
A module loader from Quora.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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