UNPKG

chamber

Version:

Javascript modules for the node and the browser

348 lines (231 loc) 8.68 kB
// # 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; }