chamber
Version:
Javascript modules for the node and the browser
348 lines (231 loc) • 8.68 kB
JavaScript
// # chamber-node.js
// chamber: a sane approach to modular package management
// -------------------------
// node-side dependencies
var _ = require('underscore');
var express = require('express');
var path = require('path');
var fs = require('fs');
var Q = require('q');
// options for when we do fs.readFile
var _fsReadOpts = { encoding: 'utf8' };
// ## Main Chamber Definition
// -------------------------
var chamber = module.exports = {};
// always loads one chamber module, or fails.
chamber.loadModule = function loadModule(moduleName, opts) {
opts = opts || {};
// log this module load
console.log('LOADING ', moduleName);
// define main module load context
var loadContext = {};
var baseDir = loadContext.baseDir = opts.baseDir;
// locals
var modules = opts.modules || {};
var moduleLocation = path.join(baseDir, moduleName)+'.js';
// ### main module loading logic
// -------------------------
// if we have already loaded this module, return the loaded one
if (modules[moduleName]) {
if (modules[moduleName].module) {
console.log('returning module')
return modules[moduleName].module;
} else {
console.log('returning module promise')
return modules[moduleName].promise;
}
}
// create a deferred promise to load this module
modules[moduleName] = Q.defer();
// create the chamber() function that the module code will call
function makeChamberFn() {
// each chamber module/file calls `chamber(function(c){` once. this is the definition of that `chamber`:
return function chamberFn(mainFn) {
// ## Prepare c for Module Invocation
// -------------------------
var c = makeC(loadContext);
c.loadModule = function innerLoadModule(moduleName, opts) {
// new opts object, to prepare for recursive calling of chamber
opts = opts || {};
// the module should refer to other modules relative to itself
opts.baseDir = baseDir || path.dirname(moduleLocation);
// share the modules
opts.modules = opts.modules || modules;
// recursively call this module loading function
return chamber.loadModule(moduleName, opts);
}
c.deferredModule = modules[moduleName];
c.resolve = c.deferredModule.resolve;
c.reject = c.deferredModule.reject;
c.notify = c.deferredModule.notify;
mainFn(c);
}
}
// use this name for the inside of the module
var __moduleName = moduleName;
// ## Module Loading and Evaluation
// -------------------------
// grab the module using native fs io
fs.readFile(moduleLocation, _fsReadOpts, function readModule(err, moduleJs) {
if (err) throw err;
// replace higher variables with locals to prevent the module from screwing things up
var fs, path, baseDir, _fsReadOpts, modules, opts, moduleName, moduleLocation;
// this is the context in which the module is evaluated
(function() {
// the 'chamber' function that every module runs
var chamber = makeChamberFn();
// ### Module Evaluation
// -------------------------
// eval the module under a try so we can identify what module breaks
try {
eval(moduleJs);
} catch (e) {
// label the error with the module which we couldn't evalutate
console.log('ERROR EVALUATING MODULE: '+ __moduleName);
throw e;
}
})();
});
return modules[moduleName].promise.then(function(module) {
modules[moduleName] = {
module: module
};
return module;
});
}
chamber.scanModule = function scanModule(moduleName, opts) {
opts = opts || {};
// log this module scan
console.log('SCANNING ', moduleName);
// define main scan context
var scanContext = {
moduleName: moduleName
};
var baseDir = scanContext.baseDir = opts.baseDir;
var moduleLocation = path.join(baseDir, moduleName)+'.js';
var moduleList = [];
// create a deferred promise to scan this module
var doScan = Q.defer();
// create the chamber() function that the module code will call
function makeChamberScanFn() {
// each chamber module/file calls `chamber(function(c){` once. this is the definition of that `chamber`:
return function chamberScanFn(mainFn) {
// ## Prepare c for Module Invocation
// -------------------------
var c = makeC(scanContext);
c.resolve = doScan.resolve;
c.reject = doScan.reject;
c.notify = doScan.notify;
var ogLoadModule = c.loadModule;
c.loadModule = function loadModuleScanShim(childModuleName, opts) {
console.log('loading '+childModuleName+' under '+scanContext.moduleName);
moduleList.push(childModuleName);
return Q.defer().promise;
}
mainFn(c);
c.resolve();
}
}
var __moduleName = moduleName;
// grab the module using native fs io
fs.readFile(moduleLocation, _fsReadOpts, function readModule(err, moduleJs) {
if (err) throw err;
// replace higher variables with locals to prevent the module from screwing things up
var fs, path, baseDir, _fsReadOpts, modules, opts, moduleName, moduleLocation;
// this is the context in which the module is evaluated
(function() {
// the 'chamber' function that every module runs
var chamber = makeChamberScanFn();
// ### Module Evaluation
// -------------------------
// eval the module under a try so we can identify what module breaks
try {
eval(moduleJs);
} catch (e) {
// label the error with the module which we couldn't evalutate
console.log('ERROR EVALUATING MODULE: '+ __moduleName);
throw e;
}
})();
});
var allModules = [
moduleName
];
return doScan.promise.then(function() {
console.log(moduleName, moduleList);
allModules = _.union(allModules, moduleList);
return Q.all(_.map(moduleList, function(module) {
console.log('FFOOOO: '+ module);
return chamber.scanModule(module, opts);
})).then(function(a) {
console.log('hello! '+moduleName,a )
return allModules;
});
});
}
function makeC(context) {
// #### context:
// * REQUIRES .baseDir
// * OPTIONAL servePath
var c = {};
// prepare c.env for node.js environmental data
c.env = {};
c.env.node = {};
// title is a human-readable identifier for the environment
c.env.title = 'Node.js '+process.version;
// node.js version
c.env.node.version = process.version;
// safe access for the module to use node's require (without using the global)
c.env.node.require = function nodeRequire(module) {
return require(module);
}
// ### Browser setup
// -------------------------
// listen to an app at a path, and use this path for browser-side refrences
c.serve = function serve(app, path) {
// save this path for when we export script tags
context.servePath = path;
var clientScriptData;
var chamberClientDir = __dirname + '/browser';
var clientScriptLocation = chamberClientDir + '/chamber-browser.js';
var qScriptLocation = chamberClientDir + '/q.js';
function sendScript(res, scriptData) {
res.setHeader('content-type', 'text/javascript');
res.send(clientScriptData);
}
app.get(path+'.js', function handleClientScriptGet(req, res) {
if (clientScriptData) return sendScript(res, clientScriptData);
else fs.readFile(clientScriptLocation, _fsReadOpts, function(err, clientScript) {
if (err) return res.send(500, err);
else {
fs.readFile(qScriptLocation, _fsReadOpts, function(err, qScript) {
if (err) return res.send(500, err);
else {
clientScriptData = qScript + ' ' + clientScript;
sendScript(res, clientScriptData);
}
});
}
});
});
app.use(path, express.static(context.baseDir));
}
// get html for script tags
c.getBrowserScriptTags = function getBrowserScriptTags(mainModule) {
if(!context.servePath) return '';
return '<script src="'+context.servePath+'.js" data-main="'+mainModule+'" data-base-dir="'+context.servePath+'"></script>';
}
// ### Module and asset loading
// -------------------------
// allows our module to load a single module
c.loadModule = function() {
throw new Error('This function must be overridden!')
}
// load a single text file
c.loadText = function loadText(textPath) {
// fs.readFile( , _fsReadOpts, function readFile(err, textData) {
// });
}
c.Q = Q;
return c;
}