Skip to content
This repository has been archived by the owner on Oct 30, 2018. It is now read-only.

loader metadata per lang #616

Merged
merged 8 commits into from
Oct 12, 2012
68 changes: 20 additions & 48 deletions lib/app/addons/ac/deploy.server.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/


/*jslint anon:true, sloppy:true, nomen:true, stupid:true*/
/*jslint anon:true, sloppy:true, nomen:true, node: true*/
/*global YUI*/


Expand All @@ -14,21 +14,9 @@
*/
YUI.add('mojito-deploy-addon', function(Y, NAME) {

var fs = require('fs'),
minify;


// TODO: [Issue 64] improve this, it's a poor man's minify.
// a. build minification into static handler?
// b. build minification into prod-build script ?
// c. build minification into server-start?
minify = function(str) {
// Remove comment blocks /* ... */ and
// remove white space at the start of lines
return str.replace(/\/\*[\s\S]*?\*\//g, '').
replace(/^[ \t\r\n]+/gm, '');
};
'use strict';

var fs = require('fs');

/**
* <strong>Access point:</strong> <em>ac.deploy.*</em>
Expand Down Expand Up @@ -75,62 +63,46 @@ YUI.add('mojito-deploy-addon', function(Y, NAME) {
contextClient,
appConfigClient,
yuiConfig = {},
fwConfig,
yuiConfigEscaped,
yuiConfigStr,
yuiModules,
yuiCombo,
yuiJsUrls = [],
yuiCssUrls = [],
viewId,
binder,
i,
id,
clientConfig = {},
clientConfigEscaped,
clientConfigStr,
initialModuleList = {},
initializer, // script for YUI initialization
type,
module,
path,
pathToRoot;

contextClient = Y.mojito.util.copy(contextServer);
contextClient.runtime = 'client';
appConfigClient = store.getAppConfig(contextClient);
clientConfig.context = contextClient;

appConfigClient.yui = appConfigClient.yui || {};
appConfigClient.yui.config = appConfigClient.yui.config || {};
yuiConfig = Y.merge({

yuiConfig = appConfigClient.yui.config;
yuiConfig.lang = contextServer.lang; // same as contextClient.lang
fetchCSS: true,
combine: true,
base: "/combo?",
comboBase: "/combo?",
root: "",
url: "/combo?yui-base/yui-base.js&loader-base/loader-base.js&" +
"loader-app-base{langPath}.js"

// If we have a "base" for YUI use it
if (appConfigClient.yui.base) {
yuiConfig.base = appConfigClient.yui.base;
yuiConfig.combine = !!appConfigClient.yui.combine;
} else {
yuiConfig.combine = true;
yuiConfig.comboBase = "/combo?";
yuiConfig.root = "";
}
}, ((appConfigClient.yui && appConfigClient.yui.config) || {}), {
lang: contextServer.lang // same as contextClient.lang
});

// adjusting the url based on {langPath} to facilitate
// the customization of the combo url.
yuiConfig.url = yuiConfig.url.replace('{langPath}',
(contextServer.lang ? '_' + contextServer.lang : ''));

clientConfig.store = store.serializeClientStore(contextClient);

// Set the YUI URL to use on the client (This has to be done
// before any other scripts are added)
assetHandler.addAsset('js', 'top',
(appConfigClient.yui.url ||
'/combo?yui-base/yui-base.js&loader-base/loader-base.js&loader-app-base.js'));

// defaults to true if missing
if (!yuiConfig.hasOwnProperty('fetchCSS') || yuiConfig.fetchCSS) {
for (i = 0; i < yuiCssUrls.length; i += 1) {
assetHandler.addCss(yuiCssUrls[i], 'top');
}
}
assetHandler.addAsset('js', 'top', yuiConfig.url);

// adding the default module for the Y.use statement in the client
initialModuleList['mojito-client'] = true;
Expand Down
178 changes: 136 additions & 42 deletions lib/app/middleware/mojito-combo-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies computations efficiently.
var libfs = require('fs'),
mime = require('mime'),
libpath = require('path'),
YUI = require(libpath.join(__dirname, '..', '..', 'yui-sandbox.js')).getYUI(),
parseUrl = require('url').parse,
logger,
NAME = 'ComboHandler',
Expand All @@ -46,6 +47,10 @@ var libfs = require('fs'),
MODULE_META_PRIVATE_ENTRIES = ['after', 'expanded', 'supersedes', 'ext', '_parsed', '_inspected',
'skinCache', 'langCache'],

REGEX_LANG_TOKEN = /\"\{langToken\}\"/g,
REGEX_LANG_PATH = /\{langPath\}/g,
REGEX_LOCALE = /\_([a-z]{2}-[A-Z]{2})$/,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to watch for simple 'en' or 'de' here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, I will generalize that.


DEFAULT_HEADERS = {
'.js': {
'Content-Type': 'application/javascript; charset=utf-8'
Expand Down Expand Up @@ -177,23 +182,54 @@ function clearCache(key) {
}
}

function processMeta(resolvedMods, modules, expanded_modules, conditions) {
function processMeta(resolvedMods, modules, expanded_modules, langs, conditions) {
var m,
l,
i,
module;
module,
name,
mod,
lang,
bundle;

for (m in resolvedMods) {
if (resolvedMods.hasOwnProperty(m)) {
module = resolvedMods[m];

mod = name = module.name;
bundle = name.indexOf('lang/') === 0;
lang = bundle && REGEX_LOCALE.exec(name);

if (lang) {
mod = mod.slice(0, lang.index); // eg. lang/foo_en-US -> lang/foo
lang = lang[1];
// TODO: validate lang
langs.push(lang); // eg. en-US
}
mod = bundle ? mod.slice(5) : mod; // eg. lang/foo -> foo

// language manipulation
// TODO: this routine is very restrictive, and we might want to
// make it optional later on.
if (module.lang) {
module.lang = ['{langToken}'];
}
if (bundle) {
module.owner = mod;
// applying some extra optimizations
module.langPack = lang || '*';
module.intl = true;
delete module.expanded_map;
}

if (module.condition && module.condition.test) {
conditions[module.name] = module.condition.test.toString();
module.condition.test = "{" + module.name + "}";
}

modules[module.name] = {};
if (module.type === 'css') {
modules[module.name] = 'css';
modules[module.name].type = 'css';
}
for (i = 0; i < MODULE_META_ENTRIES.length; i += 1) {
if (module[MODULE_META_ENTRIES[i]]) {
Expand All @@ -211,6 +247,25 @@ function processMeta(resolvedMods, modules, expanded_modules, conditions) {
}


function produceMeta(meta, name) {
var token = '',
path = '',
lang = REGEX_LOCALE.exec(name);

if (lang && lang[1] && meta[lang[1]]) {
lang = lang[1]; // eg. en-US
token = '"' + lang + '"';
path = '_' + lang;
meta = meta[lang];
} else {
meta = meta['*']; // default in case they use invalid lang
}
return LOADER_MODULE_TEMPLATE
.replace('{metadata}', meta)
.replace(REGEX_LANG_TOKEN, token)
.replace(REGEX_LANG_PATH, path);
}

/*
* Static file server.
*
Expand All @@ -226,59 +281,103 @@ function processMeta(resolvedMods, modules, expanded_modules, conditions) {
* @return {Function}
* @api public
*/
function staticProvider(store, globalLogger, Y) {
logger = globalLogger;
var appConfig = store.getStaticAppConfig(),
function staticProvider(store, globalLogger) {
var appConfig = store.getAppConfig(store.getStaticContext()),
options = appConfig.staticHandling || {},
cache = options.cache,
maxAge = options.maxAge,
urls = store.getAllModulesURLs(),
lang,

// collecting client side metadata
mojits = store.yui.getConfigAllMojits('client', {}),
shared = store.yui.getConfigShared('client', {}, false),
modules_config = Y.merge((mojits.modules || {}), (shared.modules || {})),
modules_config,
Y,
loader,
resolved,
appMetaData,
appResolvedMetaData,
appMetaData = {},
appResolvedMetaData = {},

// other structures
langs = ['*'], // language wildcard
expanded_modules = {}, // expanded meta (including fullpaths)
modules = {}, // regular meta (a la loader-yui3)
conditions = {}, // hash to store conditional functions
name;
name,
i;

logger = globalLogger;

Y = YUI({
fetchCSS: true,
combine: true,
base: "/combo?",
comboBase: "/combo?",
root: ""
}, ((appConfig.yui && appConfig.yui.config && appConfig.yui.config.config) || {}));

modules_config = Y.merge((mojits.modules || {}), (shared.modules || {}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fine for now, but i'd like to see us adopt a pattern that doesn't create objects that might not be necessary. so anywhere we could check for null/undefined instead of creating objects which affect memory consumption we should.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed.

Y.applyConfig({
modules: modules_config,
useSync: true
});
Y.use('loader');

// using the loader at the server side to compute the loader metadata
// to avoid loading the whole thing on demand.
loader = new Y.Loader(Y.merge({
ignoreRegistered: true,
modules: modules_config
}, {
loader = new Y.Loader({
require: Y.Object.keys(modules_config)
}));
});
resolved = loader.resolve(true);

// Need to make a copy otherwise the changes we make deep in this structure
// will bleed into the loader, especially causing condition.test to fail.
resolved = Y.mojito.util.copy(resolved);

if (cache && !maxAge) {
maxAge = cache;
}
maxAge = maxAge || 0;

processMeta(resolved.jsMods, modules, expanded_modules, conditions);
processMeta(resolved.cssMods, modules, expanded_modules, conditions);
processMeta(resolved.jsMods, modules, expanded_modules, langs, conditions);
processMeta(resolved.cssMods, modules, expanded_modules, langs, conditions);

for (i = 0; i < langs.length; i += 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slight performance gain if we cache length before the loop.

lang = langs[i];

appMetaData[lang] = {};
appResolvedMetaData[lang] = {};

for (name in expanded_modules) {
if (expanded_modules.hasOwnProperty(name)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slight performance gain to cache expanded_modules[name] as 'slot' or whatever rather than reaccessing it repeatedly through the [] operator.

if (expanded_modules[name].owner &&
!expanded_modules[expanded_modules[name].owner]) {
// if there is not a module corresponding with the lang pack
// that means the controller doesn't have client affinity,
// in that case, we don't need to ship it.
continue;
}
if ((lang === '*') ||
(expanded_modules[name].langPack === '*') ||
(!expanded_modules[name].langPack) ||
(lang === expanded_modules[name].langPack)) {

appMetaData[lang][name] = modules[name];
appResolvedMetaData[lang][name] = expanded_modules[name];

}
}
}

appMetaData = JSON.stringify(modules);
appResolvedMetaData = JSON.stringify(expanded_modules);
appMetaData[lang] = JSON.stringify(appMetaData[lang]);
appResolvedMetaData[lang] = JSON.stringify(appResolvedMetaData[lang]);

for (name in conditions) {
if (conditions.hasOwnProperty(name)) {
appMetaData = appMetaData.replace('"{' + name + '}"', conditions[name]);
appResolvedMetaData = appResolvedMetaData.replace('"{' + name + '}"', conditions[name]);
for (name in conditions) {
if (conditions.hasOwnProperty(name)) {
appMetaData[lang] = appMetaData[lang]
.replace('"{' + name + '}"', conditions[name]);
appResolvedMetaData[lang] = appResolvedMetaData[lang]
.replace('"{' + name + '}"', conditions[name]);
}
}

}


Expand All @@ -303,7 +402,7 @@ function staticProvider(store, globalLogger, Y) {
return next();
}

logger.log('serving static path: ' + url.pathname, 'debug', 'static-handler');
logger.log('serving combo url: ' + url.query, 'debug', NAME);

// YIV might be messing around with the querystring params
// trying to formalize them by adding = and transforming /
Expand Down Expand Up @@ -363,19 +462,21 @@ function staticProvider(store, globalLogger, Y) {
// so errors can be found early on.
for (i = 0; i < files.length; i += 1) {

// something like foo/bar-min.js should become just "bar"
module = libpath.basename(files[i], ext).
replace(/\-(min|debug)$/, '');
// something like:
// - foo/bar-min.js becomes "bar"
// - foo/lang/bar_en-US.js becomes "lang/bar_en-US"
module = (files[i].indexOf('/lang/') >= 0 ? 'lang/' : '') +
libpath.basename(files[i], ext).replace(/\-(min|debug)$/, '');

if (module === 'loader-app-base') {
if (module.indexOf('loader-app-base') === 0) {
result[i] = {
fullpath: module,
content: LOADER_MODULE_TEMPLATE.replace('{metadata}', appMetaData)
content: produceMeta(appMetaData, module)
};
} else if (module === 'loader-app-full') {
} else if (module.indexOf('loader-app-full') === 0) {
result[i] = {
fullpath: module,
content: LOADER_MODULE_TEMPLATE.replace('{metadata}', appResolvedMetaData)
content: produceMeta(appResolvedMetaData, module)
};
} else if (urls[module]) {
result[i] = {
Expand Down Expand Up @@ -418,13 +519,6 @@ function staticProvider(store, globalLogger, Y) {
}
}
}
} else {
logger.log('Error producing combo: ' + files, 'error', NAME);
// this should never happen, because an invalid
// module error should happen before reaching this.
res.writeHead(400);
res.end(undefined);
return;
}

};
Expand Down
Loading