compound
Version:
CompoundJS - MVC framework for NodeJS
316 lines (277 loc) • 8.86 kB
JavaScript
var fs = require('fs'),
path = require('path'),
events = require('events'),
util = require('util'),
existsSync = fs.existsSync || path.existsSync,
exists = fs.exists || path.exists,
Map = require('railway-routes').Map,
ControllerBridge = require('./controller-bridge'),
compoundUtils = require('./utils'),
controllerExtensions = require('./controller-extensions'),
helpers = require('./helpers'),
isServerSide = typeof window === 'undefined',
kontroller = require('kontroller'),
i18n = require('./i18n');
var express;
try {
express = require('express');
} catch(err) {
}
module.exports = Compound;
/**
* Global compound API singleton.
* Available everywhere in project.
*
*/
function Compound(app, root) {
var compound = this;
this.models = {};
this.__localeData = {};
this.structure = {
paths: {
controllers: {},
models: {},
presenters: {},
views: {},
helpers: {},
errors: {}
},
controllers: {},
models: {},
presenters: {},
views: {},
helpers: {},
errors: {},
register: function() {}
};
this.locales = [];
this.utils = compoundUtils;
this.controllerExtensions = controllerExtensions;
this.helpers = helpers;
this.parent = null;
this.root = root || process.cwd();
this.errors = this.structure.errors;
this.elements = [];
if (app) {
app.on('mount', function(parent) {
if (parent.compound) {
compound.parent = parent.compound;
} else if (parent.parent && parent.parent.compound) {
compound.parent = parent.parent.compound;
}
if (compound.parent) {
compound.parent.elements.push(compound);
}
});
app.compound = compound;
app.models = compound.models;
app.root = root;
compound.app = app;
}
this.controller = kontroller.BaseController;
this.i18n = i18n;
this.controllerBridge = new ControllerBridge(this);
this.bridge = this.controllerBridge.uniCaller.bind(this.controllerBridge);
this.expressRouter = express && express.Router ? express.Router() : null;
this.map = new Map(this.expressRouter || app, this.bridge);
if (this.constructor.name === 'CompoundClient') {
return;
}
this.initialized = false;
process.nextTick(function() {
if (!compound.initialized) {
compound.init(root);
}
});
}
util.inherits(Compound, events.EventEmitter);
/**
* Initialize compound application:
*
* - start http server
* - run configurators (config/environment, config/environments/{env})
* - add routes
* - init extensions (including ORM and db/schema)
* - init assets compiler
* - init models
* - run initializers `config/initializers/*`
* - locales
* - observers
* - assets
*
* @param {Object} app - express server, may contain optional `root` member.
* @return {Compound} compound - compound app descriptor.
*/
Compound.prototype.init = function initCompound(root) {
var compound = this;
compound.initialized = true;
root = root || compound.root;
// run environment.{js|coffee}
// and environments/{test|development|production}.{js|coffee}
compound.emit('configure');
if (isServerSide && compound.app) {
compound.configure(root);
}
compound.emit('after configure');
// controllers should be loaded before extensions
// compound.controller.init(root);
// extensions should be loaded before server startup
compound.emit('extensions', compound);
compound.extensions(root);
compound.emit('after extensions', compound);
compound.emit('routes', compound.map, compound);
if (isServerSide) {
var routesFile = root + '/config/routes', routesFileName;
if (existsSync(routesFile + '.js')) {
routesFileName = routesFile + '.js';
} else if (existsSync(routesFile + '.coffee')) {
routesFileName = routesFile + '.coffee';
}
if (routesFileName) {
compound.map.addRoutes(routesFileName, compound.bridge, compound);
}
}
compound.emit('after routes', compound.map, compound);
if ('function' === typeof compound.loadStructure) {
compound.loadStructure(root);
}
compound.emit('structure', compound.structure, compound);
// init models in app/models/*
require('./models')(compound, root);
compound.emit('models', compound.models, compound);
// run config/initializers/*
if (isServerSide && compound.app) {
compound.runInitializers(root);
}
compound.emit('initializers', compound);
compound.i18n(compound, root);
compound.emit('ready', compound);
return compound;
};
Compound.prototype.model = function(model, caseSensitive) {
if (typeof model === 'function') {
var name = model.modelName || model.name;
if (!name) {
throw new Error('Named function or jugglingdb model required');
}
this.models[name] = model;
return model;
}
if (!caseSensitive) {
model = model.toLowerCase();
}
var foundModel;
for (var i in this.models) {
if (model === i || !caseSensitive && model === i.toLowerCase()) {
foundModel = this.models[i];
}
}
return foundModel;
};
Compound.prototype.presenter = function(name) {
return this.structure.presenters[name];
};
Compound.prototype.loadConfigs = function loadConfigs(dir) {
var compound = this, app = compound.app;
if (!app) {
return;
}
fs.readdirSync(dir).forEach(function(file) {
if (file[0] === '.' || file.match(/^(Roco|environment|routes|autoload)\.(js|coffee|json|yml|yaml)$/)) {
return;
}
var filename = path.join(dir, file);
var basename = path.basename(filename, path.extname(filename));
var stats = fs.statSync(filename);
if (stats.isFile()) {
var conf = require(filename);
if ('function' === typeof conf) {
conf(compound);
} else {
app.set(basename, conf[app.get('env')]);
}
}
});
};
/**
* Run app configutators in `config/environment` and `config/environments/env`.
* @param {Compound} compound - compound app descriptor.
*/
Compound.prototype.configure = function configureApp(root) {
var compound = this;
var app = compound.app;
root = root || compound.root;
var mainEnv = root + '/config/environment';
if (root === compound.root) {
app.set('views', root + '/app/views');
}
if (!requireIfExists(compound, mainEnv + '.js')) {
requireIfExists(compound, mainEnv + '.coffee');
}
var supportEnv = app.root + '/config/environments/' + app.settings.env;
if (!requireIfExists(compound, supportEnv + '.js')) {
requireIfExists(compound, supportEnv + '.coffee');
}
};
/**
* Require `module` if it exists
*
* @param {Compound} compound - express app.
* @param {String} module - path to file.
* @return {Boolean} success - returns true when required file exists.
*/
function requireIfExists(compound, module) {
if (fs.existsSync(module)) {
requireFun(module)(compound);
return true;
} else {
return false;
}
}
function requireFun(filename) {
var mod, err;
try {
mod = require(filename);
} catch (e) {
err = e;
}
if (typeof mod !== 'function') {
console.log('WARNING: ', filename,
'should export function(compound) but given ' + typeof mod);
}
if (err) {
throw err;
}
if (typeof mod === 'function') {
return mod;
} else {
return function() {};
}
}
/**
* Run initializers in sandbox mode
*
* @param {Compound} compound - compound descriptor.
*/
Compound.prototype.runInitializers = function runInitializers(root) {
var queue, pattern, compound = this, initializersPath = path.join(
root || compound.root, 'config', 'initializers');
pattern = compound.app.get('ignore initializers pattern') || /^\./;
if (existsSync(initializersPath)) {
queue = fs.readdirSync(initializersPath).map(function(file) {
if (file.match(pattern)) return false;
return requireFun(path.join(initializersPath, file));
}).filter(Boolean);
next();
}
function next() {
var initializer = queue.shift();
if (!initializer) return;
if (initializer.length === 2) {
initializer(compound, next);
} else {
initializer(compound);
next();
}
}
};