burn
Version:
Super lightweight JS module/asset loader
267 lines (232 loc) • 7.26 kB
JavaScript
/*
* BurnJS Loader
*
* Browser-side loader for our lightweight AMD and
* Node exports implementation.
*
* This file is useless on its own, and is used in
* conjunction with the browser pack compiler
*/
;(function(config) {
/*
* Small assertion function
* Seriously, this shouldn't need to be defined. Fucking JS.
*/
var assert = function(condition, msg) {
msg = msg ? msg : "Uncaught assertion"
if (!condition) {
throw new Error(msg);
}
}
/*
* Small logger function
*/
var log = function(msg) {
window.console && console.log(msg);
}
// Imported module instances
var __CACHE__ = {};
/*
* Create new module scope
*/
var newModuleScope = function(module_name, factory) {
var self = this;
self.module = {'exports':{}};
self.module_name = module_name
/*
* Include loader
*
* An alternative (but optional) loader for modules
* which are incompatible with the AMD/CommonJS style
* loaders.
*
* This is NOT a shim, it does not try and do anything
* clever, it just runs the module with `window` as `this`
* context and exports/modules/require/define not
* exposed as either locals or globals.
*
* XXX: In future inspect the window object before/after
* inclusion to determine if anything has been
* added/removed from global namespace.
*
* @param name: (string) Module name
*/
var include = function(name) {
var scope = new Object();
scope.amd = false;
scope.ready = false;
assert((name in config.modules), "No such module: "+name);
if (name in __CACHE__) {
assert(!scope.amd, "Cannot require.include() on preloaded AMD module: "+name);
throw new Error("Cannot require.include() twice on the same module: "+name);
}
__CACHE__[name] = scope;
var factory = config.modules[name];
factory.call(window);
scope.ready = true;
}
/*
* Asset loader
*
* Used to fetch assets from our bundle
*/
var assets = function(name) {
this.get = function(name) {
assert(name in config.assets, "No such asset: "+name);
return config.assets[name];
}
this.all = function() {
return config.assets;
}
return this;
}
/*
* Lightweight implementation of AMD.define()
* https://github.com/amdjs/amdjs-api/wiki/AMD#define
*
* This is only a partial implementation because we
* do not need all the functionality of AMD, we only
* need to be able to simulate it. If you need full
* functionality, then use r.js/almond.js/browserify
*/
var define = function() {
// default arguments
var id = self.module_name;
var deps = ['require', 'module', 'exports'];
var factory = null;
// optional arguments as per AMD spec
if (arguments.length == 3) {
id = arguments[0];
deps = arguments[1];
factory = arguments[2];
} else if (arguments.length == 2) {
deps = arguments[0];
factory = arguments[1];
} else if (arguments.length == 1) {
factory = arguments[0];
}
// sanity checks
assert(typeof id === 'string', "id expected string");
assert(typeof deps === 'object', "id expected object");
assert(typeof factory === 'function', "factory expected function");
// fetch deps
var requiredDeps = [];
deps.forEach(function(name) {
requiredDeps.push(require(name));
});
// create instance
self.module.exports = factory.apply(self.module.exports, requiredDeps);
}
// fix define to declare AMD support
define.amd = true;
/*
* Lightweight implementation of AMD.require()
* https://github.com/amdjs/amdjs-api/wiki/require
*/
var require = function() {
// HANDLES: require(Array, Function)
if (arguments.length === 2) {
// handle args
var deps = arguments[0];
var factory = arguments[1];
assert(typeof deps === 'object', "Expected deps to be object");
assert(typeof factory === 'function', "Expected factory to be object");
// fetch deps
var requiredDeps = [];
deps.forEach(function(name) {
requiredDeps.push(require(name));
});
// call factory
return factory.apply(self.module.exports, requiredDeps);
}
// HANDLES: require(String)
else if (arguments.length === 1) {
// handle args
var name = arguments[0];
assert(typeof name === 'string', "Expected name to be string");
// handle reserved names
switch(name) {
case 'define':
return define;
case 'require':
return require;
case 'exports':
return self.module.exports;
case 'module':
return self.module;
case 'assets':
return new assets();
case 'include':
return include;
}
// return instance
var source_module = module_name;
return new importModule(name);
} else {
throw new Error("require() invalid args")
}
};
// expose some helpers
//require.asset = asset;
//require.include = include;
// expose some globals
self.require = require;
factory.call(window, require, self.module, self.module.exports, define);
return self;
}
/*
* Import module from bundle
*
* If module is already imported then return cached
* instance, otherwise create and return new instance.
*
* @param name: (string/object) Module name or list of names
* @returns: Module instance or list of instances
*/
var importModule = function(name) {
// sanity checks
var self = this;
var module_name = name;
assert((module_name in config.modules), "No such module: "+module_name);
// use cached instance
var m = __CACHE__[module_name];
if (m) {
if (!m.ready) {
throw new Error("Circular dependancy error: "+module_name);
}
if (!m.amd) {
return;
}
return m.scope.module.exports;
}
// register in cache
__CACHE__[module_name] = self;
log("burn: module import: "+name);
// create local scope
var factory = config.modules[module_name];
// used to catch circular dependancies
self.ready = false;
self.amd = true;
// create new module scope
self.scope = new newModuleScope(name, factory);
self.ready = true;
return self.scope.module.exports;
};
/*
* Entry point loader
*/
(function() {
// ensure require/define are not already specified
assert(typeof require == "undefined", "require() already implemented");
assert(typeof define == "undefined", "define() already implemented");
config.entry_order.forEach(function(name) {
var factory = config.entry[name];
new newModuleScope(name, factory);
});
// attach to window as magic
var factory = function() { }
var scope = new newModuleScope('__WINDOW__', factory);
window.require = scope.require;
window.define = scope.define;
})()
})(__CONFIG__);