-
Notifications
You must be signed in to change notification settings - Fork 214
loader metadata per lang #616
Changes from 7 commits
4ad69c2
48b70c9
038ef1e
4deb1b0
50f7004
a64623f
dc674e2
000013f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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', | ||
|
@@ -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})$/, | ||
|
||
DEFAULT_HEADERS = { | ||
'.js': { | ||
'Content-Type': 'application/javascript; charset=utf-8' | ||
|
@@ -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]]) { | ||
|
@@ -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. | ||
* | ||
|
@@ -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 || {})); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]); | ||
} | ||
} | ||
|
||
} | ||
|
||
|
||
|
@@ -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 / | ||
|
@@ -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] = { | ||
|
@@ -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; | ||
} | ||
|
||
}; | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.