/*
* Copyright (c) 2013, Yahoo! Inc. All rights reserved.
* Copyrights licensed under the New BSD License.
* See the accompanying LICENSE file for terms.
*/
/*jslint node:true, nomen: true */
/**
The `express-yui/lib/server` provides a set of features
to control a YUI instance on the server side.
@module express-yui/lib/server
**/
'use strict';
var libpath = require('path'),
utils = require('./utils'),
debug = require('debug')('express:yui:server');
/**
Provides a set of features to control a YUI instance on the server side.
@class server
@static
@uses *path, utils
@extensionfor express-yui/lib/yui
*/
module.exports = {
/**
Create a synthetic group off the bundle renference to register its modules
into the server and/or client Y instance so they can be used thru `app.yui.use()`.
@method registerBundle
@public
@param {Object} bundle A locator bundle reference.
@chainable
**/
registerBundle: function (bundle) {
var groups = {},
modules,
key;
if (bundle.yui) {
if (bundle.yui.client && bundle.yui.metaModuleName) {
// storing client modules
// - we don't own it, locator does it
for (key in bundle.yui.client) {
if (bundle.yui.client.hasOwnProperty(key)) {
if (this._clientModules.hasOwnProperty(key) && this._clientModules[key].group !== bundle.yui.client[key].group) {
debug("Module name collision on the client: '%s' is used for both group '%s' and group '%s'",
key, this._clientModules[key].group, bundle.yui.client[key].group);
}
this._clientModules[key] = utils.clone(bundle.yui.client[key]);
}
}
// add the meta module into the core structure
// to make sure it gets attached to Y upfront on the client side
this.registerGroup(bundle.name, bundle.buildDirectory, bundle.yui.metaModuleName);
}
if (bundle.yui.server) {
debug('Registering group "%s" for server side, and using it from "%s"',
bundle.name, bundle.buildDirectory);
// provision modules for server side
groups[bundle.name] = {
base: libpath.normalize(bundle.buildDirectory + '/'), // TODO: what if two modules have the same name but different affinity?
combine: false
};
// we don't own it, locator does it, and yui will potentially augment it
modules = utils.clone(bundle.yui.server);
// storing server modules
// - we don't own it, locator does it
for (key in modules) {
if (modules.hasOwnProperty(key)) {
if (this._serverModules.hasOwnProperty(key) && this._serverModules[key].group !== modules[key].group) {
debug("Module name collision on the server: '%s' is used for both group '%s' and group '%s'",
key, this._serverModules[key].group, modules[key].group);
}
this._serverModules[key] = modules[key];
}
}
// applying the new group config and modules globally and to the current Y instance when possible
this.YUI.applyConfig({ groups: groups });
this.YUI.applyConfig({ modules: modules });
if (this._Y) {
this._Y.applyConfig({ groups: groups });
this._Y.applyConfig({ modules: modules });
}
}
}
return this;
},
/**
Waits for the app to be ready, including the YUI instance to notify back that the
`ready` state of the app was reached by calling the `callback`. The ready state is
bound to the locator instance mounted into the express app thru `app.set('locator', locator);`
and depending on `app.get('locator').ready.then()` promise result, the ready state
will be reached or not.
app.yui.ready(function (err) {
if (err) {
throw err;
}
// do something!
});
@method ready
@param {Function} callback when the app is ready. If an error occurr, the error object
will be passed as the first argument of the callback function.
@public
**/
ready: function (callback) {
var self = this,
app = this._app,
locator = app.get('locator');
if (!locator) {
debug('Call `app.set("locator", locatorObj)` before extending the `express` app with `express-yui`');
throw new Error('Locator instance should be mounted');
}
// there is a possibility that callback fails, so we trick it to
// outsource the execution of it to avoid calling it twice
function end() {
setTimeout.apply(null, [callback, 0].concat(Array.prototype.slice.apply(arguments)));
}
locator.ready.then(function () {
var bundleNames = locator.listBundleNames(),
bundle;
bundleNames.forEach(function (bundleName) {
bundle = locator.getBundle(bundleName);
self.registerBundle(bundle);
});
end();
})["catch"](end);
},
/**
Creates a YUI Instance, and wraps the call to `Y.require()` to work appropiatly on
the server side. The `require` method can be called once the `ready` state is achieved
thru `app.yui.ready()`.
app.yui.ready(function (err) {
if (err) {
throw err;
}
app.yui.require('json-stringify', function (Y, imports) {
Y.JSON.stringify({ entry: 'value' });
});
});
@method require
@public
**/
require: function () {
var config = this.config(),
Y = this._Y,
modules,
callback,
imports;
if (!Y) {
config = utils.clone(config);
// cleaning up the config for to add on the server specific config
delete config.modules;
delete config.groups;
this.YUI.applyConfig(config);
this.YUI.applyConfig({
base: libpath.normalize(this.path + '/'),
combine: false
});
Y = this._Y = this.YUI({
useSync: true,
loadErrorFn: function (Y, i, o) {
debug('--> Something really bad happened when trying to load yui modules on the server side');
debug('--> ' + o.msg);
if (Y.config.modules) {
debug('--> Y.config.modules = ' + JSON.stringify(Y.config.modules));
}
if (Y.config.groups) {
debug('--> Y.config.groups = ' + JSON.stringify(Y.config.groups));
}
}
});
// patching serverside Y
if (this._patches && this._patches.length) {
this._patches.forEach(function (patch) {
patch(Y, Y.Env._loader);
}, this);
}
}
// attaching modules
// in case a callback is passed, we should consider that
modules = Array.prototype.slice.call(arguments, 0);
if (modules.length > 0 && (typeof modules[modules.length - 1] === 'function')) {
callback = modules.pop();
}
if (modules.length) {
try {
Y.require(modules, function (Y, __imports__) {
// capture imports in outer scope
imports = __imports__;
});
} catch (e) {
console.error('error attaching modules: ' + modules);
console.error(e);
console.error(e.stack);
}
}
if (callback) {
// support `use()` or `require()` callback signature
if (imports) {
callback(Y, imports);
} else {
callback(Y);
}
}
},
/**
Creates a YUI Instance, and wraps the call to `Y.use()` to work appropiatly on
the server side. The `use` method can be called once the `ready` state is achieved
thru `app.yui.ready()`.
app.yui.ready(function (err) {
if (err) {
throw err;
}
app.yui.use('json-stringify', function (Y) {
Y.JSON.stringify({ entry: 'value' });
});
});
@method use
@public
@return {Object} Y instance
**/
use: function () {
this.require.apply(this, arguments);
return this._Y;
}
};