diff --git a/lib/app/addons/ac/deploy.server.js b/lib/app/addons/ac/deploy.server.js
index 6710576f2..cdab14b12 100644
--- a/lib/app/addons/ac/deploy.server.js
+++ b/lib/app/addons/ac/deploy.server.js
@@ -16,8 +16,6 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) {
'use strict';
- var fs = require('fs');
-
/**
* Access point: ac.deploy.*
* Provides ability to create client runtime deployment HTML
@@ -59,13 +57,16 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) {
var store = this.rs,
contextServer = this.ac.context,
+
appConfigServer = store.getAppConfig(contextServer),
+ appGroupConfig = store.yui.getAppGroupConfig(),
+ seedFiles = store.yui.getAppSeedFiles(contextServer),
+
contextClient,
appConfigClient,
yuiConfig = {},
yuiConfigEscaped,
yuiConfigStr,
- closestLang,
viewId,
i,
clientConfig = {},
@@ -91,8 +92,9 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) {
// a fragment to prepend to the path attribute when
// when building combo urls
root: Y.version + '/build/',
- seed: "/static/combo?yui-base.js&loader-base.js&" +
- "loader-yui3.js&loader-app-base{langPath}.js&loader.js"
+ groups: {
+ app: appGroupConfig
+ }
}, ((appConfigClient.yui && appConfigClient.yui.config) || {}), {
lang: contextServer.lang // same as contextClient.lang
@@ -103,15 +105,30 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) {
delete appConfigClient.yui.config;
}
- // adjusting the seed based on {langPath} to facilitate
- // the customization of the combo url.
- closestLang = store.yui.getClosestLang(contextServer.lang);
- yuiConfig.seed = yuiConfig.seed.replace('{langPath}',
- (closestLang ? '_' + closestLang : ''));
- // Set the YUI URL to use on the client (This has to be done
- // before any other scripts are added)
- assetHandler.addAsset('js', 'top', yuiConfig.seed);
+ // Set the YUI seed file to use on the client. This has to be done
+ // before any other scripts are added and can be controlled through
+ // application.json->yui->config->seed in a form of
+ // a array with the list of modules or fullpath urls.
+ if (appGroupConfig.combine === false) {
+
+ // if the combo is disabled, then we need to insert one by one
+ // this is useful for offline and hybrid apps where the combo
+ // does not work.
+ for (i = 0; i < seedFiles.length; i += 1) {
+ assetHandler.addAsset('js', 'top', appGroupConfig.base +
+ appGroupConfig.root + seedFiles[i]);
+ }
+
+ } else {
+
+ // using combo for the seed files
+ assetHandler.addAsset('js', 'top', appGroupConfig.comboBase +
+ appGroupConfig.root + seedFiles.join(appGroupConfig.comboSep +
+ appGroupConfig.root));
+
+ }
+
// adding the default module for the Y.use statement in the client
initialModuleList['mojito-client'] = true;
diff --git a/lib/app/addons/rs/url.js b/lib/app/addons/rs/url.js
index dde417fc0..2da108688 100644
--- a/lib/app/addons/rs/url.js
+++ b/lib/app/addons/rs/url.js
@@ -4,7 +4,7 @@
* See the accompanying LICENSE file for terms.
*/
-/*jslint anon:true, sloppy:true, nomen:true, stupid:true*/
+/*jslint anon:true, sloppy:true, nomen:true, stupid:true, node:true */
/*global YUI*/
@@ -18,7 +18,10 @@
*/
YUI.add('addon-rs-url', function(Y, NAME) {
- var libfs = require('fs'),
+ 'use strict';
+
+ var libfs = require('fs'),
+ liburl = require('url'),
libpath = require('path'),
existsSync = libfs.existsSync || libpath.existsSync,
URL_PARTS = ['frameworkName', 'appName', 'prefix'];
@@ -61,9 +64,6 @@ YUI.add('addon-rs-url', function(Y, NAME) {
this.config[part] = defaults[part] || '';
}
}
-
- // FUTURE: deprecate appConfig.assumeRollups
- this.assumeRollups = this.config.assumeRollups || appConfig.assumeRollups;
},
@@ -98,10 +98,7 @@ YUI.add('addon-rs-url', function(Y, NAME) {
}
// Server-only framework mojits like DaliProxy and HTMLFrameMojit
- // should never have URLs associated with them. This never used
- // to be an issue until we added the "assumeRollups" feature to
- // preload JSON specs for specific mojits during the compile step
- // (`mojito compile json`) for Livestand.
+ // should never have URLs associated with them.
if ('shared' !== mojit && 'mojito' === mojitRes.source.pkg.name) {
mojitControllerRess = store.getResourceVersions({mojit: mojit, id: 'controller--controller'});
if (mojitControllerRess.length === 1 &&
@@ -124,7 +121,6 @@ YUI.add('addon-rs-url', function(Y, NAME) {
ress = store.getResourceVersions({mojit: mojit});
for (r = 0; r < ress.length; r += 1) {
res = ress[r];
-
skip = false;
if ('config' === res.type) {
skip = true;
@@ -177,18 +173,23 @@ YUI.add('addon-rs-url', function(Y, NAME) {
_calcResourceURL: function(res, mojitRes) {
var fs = res.source.fs,
relativePath = fs.fullPath.substr(fs.rootDir.length + 1),
- urlParts = [],
- rollupParts = [],
- rollupFsPath;
+ urlParts = [liburl.resolve('/', (this.config.prefix || 'static'))];
- // Don't clobber a URL calculated by another RS addon.
- if (res.hasOwnProperty('url')) {
+ // Don't clobber a URL calculated by another RS addon, or bother to
+ // proceed for server affinity resources that don't need uris
+ if (res.hasOwnProperty('url') || ('server' === res.affinity.affinity)) {
return;
}
- if (this.config.prefix) {
- urlParts.push(this.config.prefix);
- rollupParts.push(this.config.prefix);
+ if (res.yui && res.yui.name) {
+ // any yui module in our app will have a fixed path
+ // that has to be really short to optimize combo
+ // urls as much as possible. This url will also work
+ // in conjuntion with "base", "comboBase" and "root"
+ // from application.json->yui->config
+ urlParts.push(res.yui.name + '.js');
+ res.url = urlParts.join('/');
+ return;
}
if ('shared' === res.mojit) {
@@ -201,42 +202,23 @@ YUI.add('addon-rs-url', function(Y, NAME) {
urlParts.push(this.config.appName);
}
}
- // fw resources are also put into the app-level rollup
- if (res.yui && res.yui.name) {
- if (this.config.appName) {
- rollupParts.push(this.config.appName);
- }
- rollupParts.push('rollup.client.js');
- rollupFsPath = libpath.join(this.appRoot, 'rollup.client.js');
- }
} else {
if ('mojit' === res.type) {
urlParts.push(res.name);
} else {
urlParts.push(res.mojit);
}
- if (res.yui && res.yui.name) {
- rollupParts.push(res.mojit);
- rollupParts.push('rollup.client.js');
- rollupFsPath = libpath.join(mojitRes.source.fs.fullPath, 'rollup.client.js');
- }
}
if ('mojit' === res.type) {
if ('shared' !== res.name) {
- res.url = '/' + urlParts.join('/');
+ res.url = urlParts.join('/');
}
return;
}
urlParts.push(relativePath);
-
- if (rollupFsPath && (this.assumeRollups || existsSync(rollupFsPath))) {
- res.url = '/' + rollupParts.join('/');
- fs.rollupPath = rollupFsPath;
- } else {
- res.url = '/' + urlParts.join('/');
- }
+ res.url = urlParts.join('/');
}
diff --git a/lib/app/addons/rs/yui.js b/lib/app/addons/rs/yui.js
index 06f5cfae6..444d39fb2 100644
--- a/lib/app/addons/rs/yui.js
+++ b/lib/app/addons/rs/yui.js
@@ -4,7 +4,7 @@
* See the accompanying LICENSE file for terms.
*/
-/*jslint anon:true, sloppy:true, nomen:true, stupid:true*/
+/*jslint anon:true, sloppy:true, nomen:true, stupid:true, node:true*/
/*global YUI*/
@@ -19,9 +19,13 @@
*/
YUI.add('addon-rs-yui', function(Y, NAME) {
+ 'use strict';
+
var libfs = require('fs'),
libpath = require('path'),
libvm = require('vm'),
+ libmime = require('mime'),
+
WARN_SERVER_MODULES = /\b(dom-[\w\-]+|node-[\w\-]+|io-upload-iframe)/ig,
MODULE_SUBDIRS = {
autoload: true,
@@ -29,8 +33,10 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
yui_modules: true
},
- yuiSandbox = require(libpath.join(__dirname, '..', '..', '..', 'yui-sandbox.js')).getYUI(),
- MODULE_META_ENTRIES = ['requires', 'use', 'optional', 'skinnable', 'after', 'condition'],
+ yuiSandboxFactory = require(libpath.join(__dirname, '..', '..', '..', 'yui-sandbox.js')),
+ syntheticStat = null,
+
+ MODULE_META_ENTRIES = ['path', 'requires', 'use', 'optional', 'skinnable', 'after', 'condition'],
// TODO: revisit this list with @davglass
MODULE_META_PRIVATE_ENTRIES = ['after', 'expanded', 'supersedes', 'ext', '_parsed', '_inspected', 'skinCache', 'langCache'],
@@ -38,45 +44,75 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
REGEX_LANG_PATH = /\{langPath\}/g,
REGEX_LOCALE = /\_([a-z]{2}(-[A-Z]{2})?)$/,
+ MODULE_PER_LANG = ['loader-app-base', 'loader-app-resolved', 'loader-yui3-base', 'loader-yui3-resolved'],
MODULE_TEMPLATES = {
+ /*
+ * This is a replacement of the original loader to include loader-app
+ * module, which represents the meta of the app.
+ */
+ 'loader-app':
+ 'YUI.add("loader",function(Y){' +
+ '},"",{requires:["loader-base","loader-yui3","loader-app"]});',
+
+ /*
+ * Use this module when you want to rely on the loader to do recursive
+ * computations to resolve combo urls for app yui modules in the client
+ * runtime.
+ * Note: This is the default config used by YUI.
+ */
'loader-app-base':
- 'YUI.add("loader-app-base",function(Y){' +
+ 'YUI.add("loader-app",function(Y){' +
'Y.applyConfig({groups:{app:Y.merge(' +
- '{static-app-group-config},' +
'((Y.config.groups&&Y.config.groups.app)||{}),' +
'{modules:{app-base}}' +
')}});' +
'},"",{requires:["loader-base"]});',
- 'loader-app-full':
- 'YUI.add("loader-app-full",function(Y){' +
+
+ /*
+ * Use this module when you want to precompute the loader metadata to
+ * avoid doing recursive computations to resolve combo urls for app yui modules
+ * in the client runtime.
+ * Note: Keep in mind that this meta is considerable bigger than "loader-app-base".
+ */
+ 'loader-app-resolved':
+ 'YUI.add("loader-app",function(Y){' +
'Y.applyConfig({groups:{app:Y.merge(' +
- '{static-app-group-config},' +
'((Y.config.groups&&Y.config.groups.app)||{}),' +
- '{modules:{app-full}}' +
+ '{modules:{app-resolved}}' +
')}});' +
'},"",{requires:["loader-base"]});',
- 'loader':
- 'YUI.add("loader",function(Y){' +
- '},"",{requires:["loader-base","loader-yui3","loader-app-base"]});',
-
- 'loader-lock':
- 'YUI.add("loader",function(Y){' +
+ /*
+ * Use this module when you want to rely on the loader to do recursive
+ * computations to resolve combo urls for yui core modules in the client
+ * runtime.
+ * Note: This is a more restrictive configuration than the default
+ * meta bundle with yui, but it is considerable smaller, which helps
+ * with performance.
+ */
+ 'loader-yui3-base':
+ 'YUI.add("loader-yui3",function(Y){' +
// TODO: we should use YUI.applyConfig() instead of the internal
// YUI.Env API, but that's pending due a bug in YUI:
// http://yuilibrary.com/projects/yui3/ticket/2532854
'YUI.Env[Y.version].modules=YUI.Env[Y.version].modules||' +
'{yui-base};' +
- '},"",{requires:["loader-base","loader-app-base"]});',
+ '},"",{requires:["loader-base"]});',
- 'loader-full':
- 'YUI.add("loader",function(Y){' +
+ /*
+ * Use this module when you want to precompute the loader metadata to
+ * avoid doing recursive computations to resolve combo urls for yui core
+ * modules in the client runtime.
+ * Note: Keep in mind that this meta is considerable bigger than "loader-yui3-base".
+ */
+ 'loader-yui3-resolved':
+ 'YUI.add("loader-yui3",function(Y){' +
// TODO: we should use YUI.applyConfig() instead of the internal
// YUI.Env API, but that's pending due a bug in YUI:
// http://yuilibrary.com/projects/yui3/ticket/2532854
'YUI.Env[Y.version].modules=YUI.Env[Y.version].modules||' +
- '{yui-full};' +
- '},"",{requires:["loader-base","loader-app-full"]});'
+ '{yui-resolved};' +
+ '},"",{requires:["loader-base"]});'
};
@@ -96,16 +132,25 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
initializer: function(config) {
this.appRoot = config.appRoot;
this.mojitoRoot = config.mojitoRoot;
+
+ // for all synthetic files, since we don't have an actual file, we need to
+ // create a stat object, in this case we use the mojito folder stat as
+ // a replacement. We make it syncronous since it is meant to be executed
+ // once during the preload process.
+ syntheticStat = libfs.statSync(libpath.join(__dirname, '../../../..'));
+
this.afterHostMethod('findResourceVersionByConvention', this.findResourceVersionByConvention, this);
this.beforeHostMethod('parseResourceVersion', this.parseResourceVersion, this);
this.beforeHostMethod('addResourceVersion', this.addResourceVersion, this);
this.beforeHostMethod('makeResourceVersions', this.makeResourceVersions, this);
this.afterHostMethod('resolveResourceVersions', this.resolveResourceVersions, this);
this.beforeHostMethod('getResourceContent', this.getResourceContent, this);
- this.yuiConfig = config.host.getStaticAppConfig().yui || {};
-
+ this.staticAppConfig = config.host.getStaticAppConfig() || {};
+ this.yuiConfig = (this.staticAppConfig.yui && this.staticAppConfig.yui.config) || {};
this.langs = {}; // keys are list of languages in the app, values are simply "true"
- this.resContents = {}; // res.id: contents
+ this.resContents = {}; // res.id: contents
+ this.appModulesRess = {}; // res.yui.name: module ress
+ this.yuiModulesRess = {}; // res.yui.name: fake ress
},
@@ -211,13 +256,76 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
return Y.merge({
combine: (this.yuiConfig.combine === false) ? false : true,
maxURLLength: this.yuiConfig.maxURLLength || 1024,
- base: "/static/",
- comboBase: "/static/combo?",
+ base: "/.",
+ comboBase: "/combo~",
+ comboSep: "~",
root: ""
}, ((this.yuiConfig.groups && this.yuiConfig.groups.app) || {}));
},
+ /**
+ * Produce the YUI seed files. This can be controlled through
+ * application.json->yui->config->seed in a form of
+ * a array with the list of full paths for all seed files.
+ * @method getAppSeedFiles
+ * @param {object} ctx the context
+ * @return {array} list of seed files
+ */
+ getAppSeedFiles: function(ctx) {
+ var closestLang = this.getClosestLang(ctx.lang),
+ files = [],
+ seed = this.yuiConfig.seed ? Y.Array(this.yuiConfig.seed) : [
+ 'yui-base',
+ 'loader-base',
+ 'loader-yui3',
+ 'loader-app',
+ 'loader-app-base{langPath}'
+ ],
+ i;
+
+ // adjusting lang just to be url friendly
+ closestLang = closestLang ? '_' + closestLang : '';
+
+ // The seed files collection is lang aware, hence we should adjust
+ // is on runtime.
+ for (i = 0; i < seed.length; i += 1) {
+ // adjusting the seed based on {langToken} to facilitate
+ // the customization of the seed file url per lang.
+ files[i] = seed[i].replace(REGEX_LANG_PATH, closestLang);
+ // verifying if the file is actually a synthetic or yui module
+ if (this.yuiModulesRess.hasOwnProperty(files[i])) {
+ files[i] = this.yuiModulesRess[files[i]].url;
+ } else if (this.appModulesRess.hasOwnProperty(files[i])) {
+ files[i] = this.appModulesRess[files[i]].url;
+ }
+ }
+
+ return files;
+ },
+
+
+ /*
+ * Aggregate all yui core files
+ * using the path of as the hash.
+ *
+ * @method getYUIURLResources
+ * @return {object} yui core resources by url
+ * @api private
+ */
+ getYUIURLResources: function () {
+ var name,
+ urls = {};
+
+ for (name in this.yuiModulesRess) {
+ if (this.yuiModulesRess.hasOwnProperty(name)) {
+ urls[this.yuiModulesRess[name].url] = this.yuiModulesRess[name];
+ }
+ }
+ return urls;
+ },
+
+
/**
* Using AOP, this is called after the ResourceStore's version.
* @method findResourceVersionByConvention
@@ -289,6 +397,8 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
res.name = res.yui.name;
res.id = [res.type, res.subtype, res.name].join('-');
this.langs[res.yui.lang] = true;
+ // caching the res
+ this.appModulesRess[res.yui.name] = res;
return new Y.Do.Halt(null, res);
}
@@ -314,6 +424,8 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
this._captureYUIModuleDetails(res);
res.name = res.yui.name;
res.id = [res.type, res.subtype, res.name].join('-');
+ // caching the res
+ this.appModulesRess[res.yui.name] = res;
return new Y.Do.Halt(null, res);
}
},
@@ -358,7 +470,9 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
//console.log('------------------------------------------- YUI makeResourceVersions');
var store = this.get('host'),
l,
+ i,
lang,
+ name,
langExt,
langs = Object.keys(this.langs),
res;
@@ -370,88 +484,58 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
res = {
source: {},
- type: 'yui-loader',
- subtype: 'rollup',
- name: '',
- affinity: 'common',
+ mojit: 'shared',
+ type: 'yui-module',
+ subtype: 'synthetic',
+ name: 'loader-app',
+ affinity: 'client',
selector: '*',
yui: {
- name: 'loader'
+ name: 'loader-app'
}
};
res.id = [res.type, res.subtype, res.name].join('-');
res.source.pkg = store.getAppPkgMeta();
- res.source.fs = store.makeResourceFSMeta(this.appRoot, 'app', '.', 'loader.js', true);
+ res.source.fs = store.makeResourceFSMeta(this.appRoot, 'app', '.', 'loader-app.js', true);
+ // adding res to cache
+ this.appModulesRess['loader-app'] = res;
store.addResourceVersion(res);
for (l = 0; l < langs.length; l += 1) {
lang = langs[l];
langExt = lang ? '_' + lang : '';
- res = {
- source: {},
- type: 'yui-loader',
- subtype: 'app-base',
- name: lang,
- affinity: 'common',
- selector: '*',
- yui: {
- name: 'loader-app-base' + langExt
- }
- };
- res.id = [res.type, res.subtype, res.name].join('-');
- res.source.pkg = store.getAppPkgMeta();
- res.source.fs = store.makeResourceFSMeta(this.appRoot, 'app', '.', 'loader-app-base' + langExt + '.js', true);
- store.addResourceVersion(res);
+ for (i = 0; i < MODULE_PER_LANG.length; i += 1) {
- res = {
- source: {},
- type: 'yui-loader',
- subtype: 'app-full',
- name: lang,
- affinity: 'common',
- selector: '*',
- yui: {
- name: 'loader-app-full' + langExt
- }
- };
- res.id = [res.type, res.subtype, res.name].join('-');
- res.source.pkg = store.getAppPkgMeta();
- res.source.fs = store.makeResourceFSMeta(this.appRoot, 'app', '.', 'loader-app-full' + langExt + '.js', true);
- store.addResourceVersion(res);
+ name = MODULE_PER_LANG[i];
- res = {
- source: {},
- type: 'yui-loader',
- subtype: 'yui-base',
- name: lang,
- affinity: 'common',
- selector: '*',
- yui: {
- name: 'loader-lock' + langExt
- }
- };
- res.id = [res.type, res.subtype, res.name].join('-');
- res.source.pkg = store.getAppPkgMeta();
- res.source.fs = store.makeResourceFSMeta(this.appRoot, 'app', '.', 'loader-lock' + langExt + '.js', true);
- store.addResourceVersion(res);
+ res = {
+ source: {},
+ mojit: 'shared',
+ type: 'yui-module',
+ subtype: 'synthetic',
+ name: [name, lang].join('-'),
+ affinity: 'client',
+ selector: '*',
+ yui: {
+ name: name + langExt
+ }
+ };
+ res.id = [res.type, res.subtype, res.name].join('-');
+ res.source.pkg = store.getAppPkgMeta();
+ res.source.fs = store.makeResourceFSMeta(this.appRoot, 'app', '.',
+ name + langExt + '.js', true);
+ // adding res to cache
+ this.appModulesRess[name + langExt] = res;
+ store.addResourceVersion(res);
+
+ }
- res = {
- source: {},
- type: 'yui-loader',
- subtype: 'yui-full',
- name: lang,
- affinity: 'common',
- selector: '*',
- yui: {
- name: 'loader-full' + langExt
- }
- };
- res.id = [res.type, res.subtype, res.name].join('-');
- res.source.pkg = store.getAppPkgMeta();
- res.source.fs = store.makeResourceFSMeta(this.appRoot, 'app', '.', 'loader-full' + langExt + '.js', true);
- store.addResourceVersion(res);
}
+
+ // we can also make some fake resources for all yui
+ // modules that we might want to serve.
+ this._precalcYUIResources();
},
@@ -486,14 +570,61 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
* @return {undefined} nothing is returned, the results are returned via the callback
*/
getResourceContent: function(res, callback) {
- var contents = this.resContents[res.id];
+ var contents = res.name && this.resContents[res.name];
if (contents) {
- callback(null, new Buffer(contents, 'utf8'), null);
+ callback(null, new Buffer(contents, 'utf8'), syntheticStat);
return new Y.Do.Halt(null, null);
}
},
+ /**
+ * Precomputes YUI modules resources, so that we don't have to at runtime.
+ * @private
+ * @method _precalcYUIResources
+ * @return {nothing}
+ */
+ _precalcYUIResources: function() {
+ var name,
+ modules,
+ mimetype,
+ charset,
+ fullpath,
+ staticHandling = this.staticAppConfig.staticHandling || {},
+ Ysandbox = yuiSandboxFactory
+ .getYUI(this.yuiConfig.filter)(Y.merge(this.yuiConfig));
+
+ // used to find the the modules in YUI itself
+ Ysandbox.use('loader');
+ modules = (new Ysandbox.Loader(Ysandbox.config)).moduleInfo;
+
+ for (name in modules) {
+ if (modules.hasOwnProperty(name)) {
+ // faking a RS object for the sake of simplicity
+ fullpath = libpath.join(__dirname,
+ '../../../../node_modules/yui', modules[name].path);
+ mimetype = libmime.lookup(fullpath);
+ charset = libmime.charsets.lookup(mimetype);
+
+ modules[name] = {
+ url: libpath.join('/', (staticHandling.prefix || 'static'),
+ 'yui', modules[name].path),
+ source: {
+ fs: {
+ isFile: true,
+ fullPath: fullpath
+ }
+ },
+ mime: {
+ type: mimetype,
+ charset: charset
+ }
+ };
+ }
+ }
+ this.yuiModulesRess = modules;
+ },
+
/**
* Precomputes YUI loader metadata, so that we don't have to at runtime.
* @private
@@ -524,13 +655,15 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
modules = {}, // regular meta (a la loader-yui3)
conditions = {}, // hash to store conditional functions
name,
- i;
+ i,
+ l;
// TODO: inline these calls, and optimize
mojits = this.getConfigAllMojits('client', {});
shared = this.getConfigShared('client', {});
- Ysandbox = yuiSandbox(Y.merge(this.yuiConfig, this.getAppGroupConfig()));
+ Ysandbox = yuiSandboxFactory
+ .getYUI(this.yuiConfig.filter)(Y.merge(this.yuiConfig));
modules_config = Ysandbox.merge((mojits.modules || {}), (shared.modules || {}));
Ysandbox.applyConfig({
@@ -607,22 +740,20 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
}
} // for each lang
- this.resContents['yui-loader-rollup-'] = MODULE_TEMPLATES.loader;
+ this.resContents['loader-app'] = MODULE_TEMPLATES['loader-app'];
- for (i = 0; i < langs.length; i += 1) {
- lang = langs[i] || '';
+ for (l = 0; l < langs.length; l += 1) {
+ lang = langs[l] || '';
- this.resContents['yui-loader-app-base-' + lang] =
- this._produceMeta('loader-app-base', lang || '*', appMetaData, yuiMetaData);
+ for (i = 0; i < MODULE_PER_LANG.length; i += 1) {
- this.resContents['yui-loader-app-full-' + lang] =
- this._produceMeta('loader-app-full', lang || '*', appMetaData, yuiMetaData);
+ name = MODULE_PER_LANG[i];
+ // populating the internal cache using name+lang as the key
+ this.resContents[([name, lang].join('-'))] =
+ this._produceMeta(name, lang || '*', appMetaData, yuiMetaData);
- this.resContents['yui-loader-yui-base-' + lang] =
- this._produceMeta('loader-lock', lang || '*', appMetaData, yuiMetaData);
+ }
- this.resContents['yui-loader-yui-full-' + lang] =
- this._produceMeta('loader-full', lang || '*', appMetaData, yuiMetaData);
}
},
@@ -723,11 +854,10 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
// module definition definitions
return MODULE_TEMPLATES[name]
- .replace('{static-app-group-config}', JSON.stringify(this.getAppGroupConfig()))
.replace('{app-base}', appMetaData.base[lang] || appMetaData.base['*'])
- .replace('{app-full}', appMetaData.full[lang] || appMetaData.full['*'])
+ .replace('{app-resolved}', appMetaData.full[lang] || appMetaData.full['*'])
.replace('{yui-base}', yuiMetaData.base[lang] || yuiMetaData.base['*'])
- .replace('{yui-full}', yuiMetaData.full[lang] || yuiMetaData.full['*'])
+ .replace('{yui-resolved}', yuiMetaData.full[lang] || yuiMetaData.full['*'])
.replace(REGEX_LANG_TOKEN, token)
.replace(REGEX_LANG_PATH, path);
},
@@ -829,9 +959,14 @@ YUI.add('addon-rs-yui', function(Y, NAME) {
*/
_makeYUIModuleConfig: function(env, res) {
var config = {
- fullpath: ('client' === env) ? res.url : res.source.fs.fullPath,
requires: (res.yui.meta && res.yui.meta.requires) || []
};
+ if ('client' === env) {
+ // using relative path since the loader will do the rest
+ config.path = res.url;
+ } else {
+ config.fullpath = res.source.fs.fullPath;
+ }
return config;
},
diff --git a/lib/app/autoload/store.server.js b/lib/app/autoload/store.server.js
index 4bc161fa0..8126c96df 100644
--- a/lib/app/autoload/store.server.js
+++ b/lib/app/autoload/store.server.js
@@ -806,7 +806,7 @@ YUI.add('mojito-resource-store', function(Y, NAME) {
for (r = 0; r < ress.length; r += 1) {
res = ress[r];
if (res.url) {
- urls[res.url] = res.source.fs.rollupPath || res.source.fs.fullPath;
+ urls[res.url] = res.source.fs.fullPath;
}
}
}
@@ -845,11 +845,6 @@ YUI.add('mojito-resource-store', function(Y, NAME) {
res = ress[r];
if (res.url && res.source.fs.isFile) {
urls[res.url] = res;
- // rewrite for the favicon.ico
- // /favicon.ico is sent to ./my_app_folder/assets/favicon.ico
- if (/\/assets\/favicon\.ico$/.test(res.url) && (res.source.fs.rootType === 'app')) {
- urls['/favicon.ico'] = res;
- }
}
}
}
@@ -874,7 +869,7 @@ YUI.add('mojito-resource-store', function(Y, NAME) {
filename;
if (res && res.source && res.source.fs && res.source.fs.isFile) {
- filename = res.source.fs.rollupPath || res.source.fs.fullPath;
+ filename = res.source.fs.fullPath;
// FUTURE [Issue 89] stat cache?
store._libs.fs.stat(filename, function(err, stat) {
diff --git a/lib/app/middleware/mojito-handler-static.js b/lib/app/middleware/mojito-handler-static.js
index 770bef290..f886840f8 100644
--- a/lib/app/middleware/mojito-handler-static.js
+++ b/lib/app/middleware/mojito-handler-static.js
@@ -19,20 +19,25 @@
*/
-/*jslint node:true, nomen:true, continue: true */
+/*jslint node:true, nomen:true */
'use strict';
/*
* Module dependencies.
*/
-var libfs = require('fs'),
- libpath = require('path'),
- libmime = require('mime'),
- YUI = require(libpath.join(__dirname, '..', '..', 'yui-sandbox.js')).getYUI(),
- parseUrl = require('url').parse,
- logger,
- NAME = 'StaticHandler';
+var liburl = require('url'),
+ libpath = require('path'),
+
+ NAME = 'StaticHandler',
+
+ // TODO: this adjustments should really be done by addons/rs/url
+ // or a designated route.
+ STATIC_ROUTER = {
+ '/favicon.ico': {mojit: 'shared', id: 'asset-ico-favicon'},
+ '/robots.txt': {mojit: 'shared', id: 'asset-txt-robots'},
+ '/crossdomain.xml': {mojit: 'shared', id: 'asset-xml-crossdomain'}
+ };
/*
* File buffer cache.
@@ -169,118 +174,6 @@ function makeContentTypeHeader(resource) {
resource.mime.charset : '');
}
-/*
- * Aggregate all yui modules from the app that are compatible with
- * client runtime using the name of the yui module as the hash.
- *
- * @method getAppModuleResources
- * @param {object} store Resource Store instance
- * @return {object} yui modules resources
- * @api private
- */
-function getAppModuleResources(store) {
- var ress,
- m,
- mojit,
- mojits,
- moduleRess = {};
-
- function processRess(ress) {
- var r,
- res;
- for (r = 0; r < ress.length; r += 1) {
- res = ress[r];
- if ('common' !== res.affinity.affinity) {
- continue;
- }
- if (res.yui && res.yui.name) {
- moduleRess[res.yui.name] = res;
- }
- }
- for (r = 0; r < ress.length; r += 1) {
- res = ress[r];
- if ('client' !== res.affinity.affinity) {
- continue;
- }
- if (res.yui && res.yui.name) {
- if (moduleRess[res.yui.name]) {
- logger.log('YUI Modules should have unique name per affinity. ' +
- 'Module [' + res.yui.name + '] has both common and ' +
- 'client affinity.', 'warn', NAME);
- }
- moduleRess[res.yui.name] = res;
- }
- }
- }
-
- ress = store.getResourceVersions({});
- processRess(ress);
-
- mojits = store.listAllMojits();
- mojits.push('shared');
- for (m = 0; m < mojits.length; m += 1) {
- mojit = mojits[m];
- ress = store.getResourceVersions({mojit: mojit});
- processRess(ress);
- }
-
- return moduleRess;
-}
-
-/*
- * Aggregate all yui core modules
- * using the name of the yui module as the hash.
- *
- * @method getYUIModuleResources
- * @param {object} appConfig Static application config
- * @return {object} yui core modules resources
- * @api private
- */
-function getYUIModuleResources(appConfig) {
- var Y,
- name,
- modules,
- mimetype,
- charset,
- fullpath;
-
- Y = YUI({
- fetchCSS: true,
- combine: true,
- base: "/static/combo?",
- comboBase: "/static/combo?",
- root: ""
- }, ((appConfig.yui && appConfig.yui.config) || {}));
-
- // used to find the the modules in YUI itself
- Y.use('loader');
- modules = (new Y.Loader(Y.config)).moduleInfo;
-
- for (name in modules) {
- if (modules.hasOwnProperty(name)) {
- // faking a RS object for the sake of simplicity
- fullpath = libpath.join(__dirname,
- '../../../node_modules/yui', modules[name].path);
- mimetype = libmime.lookup(fullpath);
- charset = libmime.charsets.lookup(mimetype);
- modules[name] = {
- source: {
- fs: {
- isFile: true,
- fullPath: fullpath
- }
- },
- mime: {
- type: mimetype,
- charset: charset
- }
- };
- }
- }
- return modules;
-}
-
-
/*
* Static file server.
*
@@ -296,21 +189,17 @@ function getYUIModuleResources(appConfig) {
* @return {Function}
* @api public
*/
-function staticProvider(store, globalLogger, Y) {
+function staticProvider(store, logger, Y) {
- var appConfig = store.getStaticAppConfig(),
- options = appConfig.staticHandling || {},
- cache = options.cache,
- maxAge = options.maxAge,
- staticRess,
- moduleRess,
- yuiRess;
-
- logger = globalLogger;
+ var appConfig = store.getStaticAppConfig(),
+ yuiRess = store.yui.getYUIURLResources(),
+ staticRess = store.getAllURLResources(),
- moduleRess = getAppModuleResources(store);
- yuiRess = getYUIModuleResources(appConfig);
- staticRess = store.getAllURLResources();
+ options = appConfig.staticHandling || {},
+ cache = options.cache,
+ maxAge = options.maxAge,
+ staticPath = liburl.resolve('/', (options.prefix || 'static') + '/'),
+ comboPath = '/combo~';
if (cache && !maxAge) {
maxAge = cache;
@@ -333,18 +222,16 @@ function staticProvider(store, globalLogger, Y) {
return;
}
- var url = parseUrl(req.url),
+ var url = liburl.parse(req.url),
path = url.pathname,
files = [],
file,
result = [],
failures = 0,
counter = 0,
- module,
resource,
i;
-
function tryToFlush() {
var headers,
content = '',
@@ -355,7 +242,7 @@ function staticProvider(store, globalLogger, Y) {
}
if (counter === files.length) {
if (failures) {
- notFound();
+ notFound(res);
return;
}
}
@@ -394,7 +281,7 @@ function staticProvider(store, globalLogger, Y) {
counter += 1;
if (err) {
logger.log('failed to read ' + path + ' because: ' + err.message, 'error', NAME);
- notFound();
+ notFound(res);
} else {
logger.log(path + ' was read from disk', 'debug', NAME);
result[index].content = data;
@@ -419,36 +306,31 @@ function staticProvider(store, globalLogger, Y) {
return;
}
-
- // combo urls are allow as well
- if (libpath.basename(url.pathname) === 'combo' && url.query) {
- // YIV might be messing around with the querystring params
- // trying to formalize them by adding = and transforming /
- // so we need to revert back to / and remove the =
- // TODO: this might not work in Windows
- files = url.query.replace(/[=]/g, '').replace(/%2F/g, '/').split('&');
- } else {
+ // combo urls are allow as well in a form of:
+ // - /combo~foo.js~bar.js
+ if (path.indexOf(comboPath) === 0) {
+ // no spaces allowed
+ files = url.pathname.split('~');
+ files.shift(); // removing te first element from the list
+ } else if (path.indexOf(staticPath) === 0 || STATIC_ROUTER.hasOwnProperty(path)) {
files = [path];
+ } else {
+ // this is not a static file
+ next();
+ return;
}
for (i = 0; i < files.length; i += 1) {
file = files[i];
- // Cache hit
if (cache && _cache[file]) {
+
+ // Cache hit
logger.log(file + ' was read from cache', 'debug', NAME);
result[i] = _cache[file];
- continue;
- }
- // something like:
- // - foo/bar-min.js becomes "bar"
- // - foo/lang/bar_en-US.js becomes "lang/bar_en-US"
- module = (file.indexOf('/lang/') >= 0 ? 'lang/' : '') +
- libpath.basename(file, libpath.extname(file)).
- replace(/\-(min|debug)$/, '');
- if (staticRess[file]) {
+ } else if (staticRess[file]) {
// geting an static file
result[i] = {
@@ -456,47 +338,31 @@ function staticProvider(store, globalLogger, Y) {
res: staticRess[file]
};
- // TODO: these adjustments should really be done by addons/rs/url
- } else if (file === '/favicon.ico') {
+ } else if (STATIC_ROUTER.hasOwnProperty(file)) {
+ // TODO: this adjustments should really be done by addons/rs/url
// geting an static favicon
result[i] = {
path: file,
- res: store.getResources('client', {}, {mojit: 'shared', id: 'asset-ico-favicon'})[0]
+ res: store.getResources('client', {}, STATIC_ROUTER[file])[0]
};
- } else if (file === '/robots.txt') {
+ } else if (yuiRess[file]) {
- // geting an static robot
+ // getting a yui library file
result[i] = {
path: file,
- res: store.getResources('client', {}, {mojit: 'shared', id: 'asset-txt-robots'})[0]
+ res: yuiRess[file]
};
- } else if (file === '/crossdomain.xml') {
+ } else if (i < (files.length - 1)) {
- // geting an static crossdomain
- result[i] = {
- path: file,
- res: store.getResources('client', {}, {mojit: 'shared', id: 'asset-xml-crossdomain'})[0]
- };
-
- // other kind of static files
- } else if (moduleRess[module]) {
-
- // geting an app module
- result[i] = {
- path: file,
- res: moduleRess[module]
- };
-
- } else if (yuiRess[module]) {
-
- // getting a yui module
- result[i] = {
- path: file,
- res: yuiRess[module]
- };
+ // Note: we are tolerants with the last file in the combo
+ // because some proxies/routers might cut the url, and
+ // the loader should be able to recover from that if we send
+ // a partial response.
+ notFound(res);
+ return;
}
diff --git a/lib/config.json b/lib/config.json
index 3e94ad2f0..e9bd9186e 100644
--- a/lib/config.json
+++ b/lib/config.json
@@ -11,6 +11,9 @@
"tunnelProxy": {
"type": "TunnelProxy"
}
+ },
+ "staticHandling": {
+ "prefix": "static"
}
},
"defaultRoutes" : {