Created
December 2, 2015 10:19
-
-
Save futurist/109ea590d7d2f64d8278 to your computer and use it in GitHub Desktop.
Mini module system for the browser with hot swapping and remote loading support, 750 bytes minified+gzipped
This file contains hidden or 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 file contains a primitive JS/resource loader and resolver that can load | |
* both local and remote files. | |
* | |
* API usage: | |
* | |
* ```js | |
* r.define("foo", function () { return "default export" }) | |
* | |
* r.define("assert", function () { | |
* return function assert(condition, message) { | |
* if (!condition) throw new Error(message) | |
* } | |
* }) | |
* | |
* r.define("main", function (require) { | |
* var assert = require("assert") | |
* | |
* assert(require("foo") === "default export", | |
* "default exports are read correctly") | |
* | |
* r.load("page/base", "/page.js", function (err, BaseComponent) { | |
* if (err != null) return displayError(err) | |
* renderComponent(document.getElementById("body"), BaseComponent) | |
* }) | |
* | |
* r.redefine("assert", function () { | |
* return function assert() { | |
* return true | |
* } | |
* }) | |
* }) | |
* ``` | |
*/ | |
!(function (r, window) { // eslint-disable-line | |
"use strict" | |
// Object.create(null) is used to avoid read-only Object.prototype methods | |
var cache = Object.create(null) | |
var factories = Object.create(null) | |
function init(name) { | |
var res = factories[name](require, cache[name]) | |
if (res != null) cache[name] = res | |
delete factories[name] | |
} | |
function require(name) { | |
if (name in factories) init(name) | |
return cache[name] | |
} | |
// Normalize the path, resolving . and .. elements. It retains trailing | |
// slashes. | |
function resolve(path) { | |
var res = [] | |
path.split("/").map(function (p) { | |
if (p === "..") { | |
// Don't go above the root - this is a noop if `res` is empty | |
res.pop() | |
} else if (p && p !== ".") { | |
// Ignore empty parts | |
res.push(p) | |
} | |
}) | |
return "/" + res.join("/") + (path.slice(-1) === "/" ? "/" : "") | |
} | |
function load(name, callback, errback) { | |
var el = document.createElement("script") | |
el.async = el.defer = true | |
el.src = name | |
el.onload = function (e) { | |
e.preventDefault() | |
e.stopPropogation() | |
// Remove the node after it loads. | |
document.body.removeChild(el) | |
callback() | |
} | |
el.onerror = function (e) { | |
e.preventDefault() | |
e.stopPropogation() | |
// Remove the node after it loads. | |
document.body.removeChild(el) | |
var xhr = new XMLHttpRequest() | |
xhr.open("GET", name.replace(/\..*?\/?$/, "/modules.json")) | |
xhr.onreadystatechange = function (data, len) { | |
if (this.readyState === 4) { | |
if (!(len = Object.keys(data = this.response) | |
.map(function (key, value) { | |
if (typeof value !== "string") { | |
throw new TypeError("Expected key " + key + " in " + | |
name.replace(/\..*?\/?$/, "/modules.json") + | |
" to be a string, but found " + data[key]) | |
} | |
return load(resolve(name + "/" + data[key]), | |
function () { | |
if (len && --len) callback() | |
}, | |
function (err) { | |
if (len) errback((len = 0, err)) | |
}) | |
}).length)) { | |
callback() | |
} | |
} | |
} | |
xhr.onerror = function (e) { | |
errback(new Error(e.type)) | |
} | |
xhr.send() | |
} | |
document.body.appendChild(el) | |
} | |
r.define = function (name, impl) { | |
if (name in cache) { | |
throw new TypeError("Module already defined: " + name) | |
} | |
if (typeof impl !== "function") { | |
throw new TypeError("Expected body to be a function") | |
} | |
cache[name] = {} | |
factories[name] = impl | |
// For convenience | |
if (name === "main") { | |
setTimeout(function () { | |
init(name) | |
}) | |
} | |
} | |
r.redefine = function (name, impl) { | |
if (typeof impl !== "function") { | |
throw new TypeError("Expected body to be a function") | |
} | |
cache[name] = {} | |
factories[name] = impl | |
setTimeout(function () { | |
init(name) | |
}) | |
} | |
/* | |
* Note that this assumes JavaScript | |
*/ | |
r.load = function (ns, name, callback) { | |
if (typeof type === "function") { | |
callback = name | |
name = ns | |
ns = null | |
} | |
load(resolve(name), function () { | |
if (ns == null) return callback() | |
return callback(null, require(ns)) | |
}, callback) | |
} | |
})(window.r = {}, window) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment