dojo-util
Version:
Dojo utilities including build system for optimizing JavaScript application performance, and DOH testing tool
384 lines (352 loc) • 12 kB
JavaScript
define([
"../buildControl",
"../fileUtils",
"../fs",
"dojo/_base/lang",
"dojo/json"
], function(bc, fileUtils, fs, lang, json){
var
computingLayers
// the set of layers being computed; use this to detect circular layer dependencies
= {},
computeLayerContents = function(
layerModule,
include,
exclude
){
// add property layerSet (a set of mid) to layerModule that...
//
// * includes the layerModule itself
// * includes dependency tree of layerModule
// * includes all modules in layerInclude and their dependency trees
// * excludes all modules in layerExclude and their dependency trees
//
// note: layerSet is built exactly as given above, so included modules that are later excluded
// are *not* in result layerSet
if(computingLayers[layerModule.mid]){
bc.log("amdCircularDependency", ["module", layerModule.mid]);
return {};
}
computingLayers[layerModule.mid] = 1;
var
includeSet = {},
visited,
includePhase,
traverse = function(module){
var mid = module.mid;
if(visited[mid]){
return;
}
visited[mid] = 1;
if(includePhase){
includeSet[mid] = module;
}else{
delete includeSet[mid];
}
if(module!==layerModule && module.layer){
var layerModuleSet = module.moduleSet || computeLayerContents(module, module.layer.include, module.layer.exclude);
for(var p in layerModuleSet){
if(includePhase){
includeSet[p] = layerModuleSet[p];
}else{
delete includeSet[p];
}
}
}else{
for(var deps = module.deps, i = 0; deps && i<deps.length; traverse(deps[i++])){
}
}
};
visited = {};
includePhase = true;
traverse(layerModule);
include.forEach(function(mid){
var module = bc.amdResources[bc.getSrcModuleInfo(mid, layerModule).mid];
if(!module){
bc.log("amdMissingLayerIncludeModule", ["missing", mid, "layer", layerModule.mid]);
}else{
traverse(module);
}
});
visited = {};
includePhase = false;
exclude.forEach(function(mid){
var module = bc.amdResources[bc.getSrcModuleInfo(mid, layerModule).mid];
if(!module){
bc.log("amdMissingLayerExcludeModule", ["missing", mid, "layer", layerModule.mid]);
}else{
traverse(module);
}
});
layerModule.moduleSet = includeSet;
delete computingLayers[layerModule.mid];
// return a copy so that clients can not mutate layerModule.moduleSet
var result = {};
for(var p in includeSet){
result[p] = includeSet[p];
}
return result;
},
insertAbsMid = function(
text,
resource
){
return (!resource.mid || resource.tag.hasAbsMid || !bc.insertAbsMids) ?
text : text.replace(/(define\s*\(\s*)(.*)/, "$1\"" + resource.mid + "\", $2");
},
pushString = function(
strings,
pair
){
strings[pair[0]] = pair[1];
},
appendStringsToCache = function(
strings,
cache
) {
for(var p in strings){
cache.push("'" + p + "':" + strings[p])
}
},
getPreloadL10nRootPath = function(
dest
){
var match = dest.match(/(.+)\/([^\/]+)$/);
return match[1] + "/nls/" + match[2];
},
flattenRootBundle = function(resource){
if(resource.flattenedBundles){
return;
}
resource.flattenedBundles = {};
var scheduledToFlatten = {};
bc.localeList.forEach(function(locale){
scheduledToFlatten[locale] = 1;
});
bc.localeList.forEach(function(locale){
var accumulator = lang.mixin({}, resource.bundleValue.root);
if(!bc.localeList.discreteLocales[locale]){
console.log(resource.mid, locale);
}else{
bc.localeList.discreteLocales[locale].forEach(function(discreteLocale){
var localizedBundle = resource.localizedSet[discreteLocale];
if(localizedBundle && localizedBundle.bundleValue){
lang.mixin(accumulator, localizedBundle.bundleValue);
}
});
var set = {};
if(locale === "ROOT"){
for(var p in resource.localizedSet) set[p] = 1;
}else{
for(var p in resource.localizedSet) if(!scheduledToFlatten[p] && p.indexOf(locale) == 0 && p.length > locale.length){
// p is a more-specific version of locale and it is not scheduled to be flattened itself
set[p] = 1;
}
}
accumulator._localized = set;
}
resource.flattenedBundles[locale] = accumulator;
}
)
;
},
getFlattenedBundles = function(
resource,
rootBundles
){
rootBundles.forEach(flattenRootBundle);
var newline = bc.newline,
rootPath = getPreloadL10nRootPath(resource.dest.match(/(.+)(\.js)$/)[1]),
mid, cache;
bc.localeList.forEach(function(locale){
cache = [];
rootBundles.forEach(function(rootResource){
cache.push("'" + rootResource.prefix + rootResource.bundle + "':" + json.stringify(rootResource.flattenedBundles[locale]) + newline);
});
mid = getPreloadL10nRootPath(resource.mid) + "_" + locale;
var flattenedResource = {
src:"*synthetic*",
dest:rootPath + "_" + locale + ".js",
pid:resource.pid,
mid:mid,
pack:resource.pack,
deps:[],
tag:{flattenedNlsBundle:1},
encoding:'utf8',
text:"define(" + (bc.insertAbsMids ? "'" + mid + "',{" : "{") + newline + cache.join("," + newline) + "});",
getText:function(){ return this.text; }
};
if(bc.insertAbsMids){
flattenedResource.tag.hasAbsMid = 1;
}
bc.start(flattenedResource);
});
},
getLayerText = function(
resource,
resourceText // ===undefined (normal case) => AMD define() the resource at the end of the layer
// ===false => put the resource in the cache
// otherwise write the value of resourceText at the end of the layer (so far only used in the writeDojo transform)
){
var newline = bc.newline,
rootBundles = [],
strings = {},
cache = [],
layer = resource.layer,
moduleSet = computeLayerContents(resource, layer.include, layer.exclude),
includeLocales = "includeLocales" in layer ? layer.includeLocales : bc.includeLocales;
for(var p in moduleSet){
// always put modules!=resource in the cache; put resource in the cache if it's a boot layer and an explicit resourceText wasn't given
if(p!=resource.mid || resourceText===false){
var module = moduleSet[p];
if(module.localizedSet && bc.localeList){
// this is a root NLS bundle and the profile is building flattened layer bundles;
// therefore, add this bundle to the set to be flattened, but don't write the root bundle
// to the cache since the loader will explicitly load the flattened bundle
rootBundles.push(module);
if(includeLocales){
// include the ROOT always
cache.push("'" + p + "':function(){" + newline + module.getText() + newline + "}");
// now include each locale in the layer
includeLocales.forEach(function(locale){
var parts = locale.split("-");
for(var i = parts.length; i > 0; i--){
var localizedSet = module.localizedSet[parts.slice(0, i).join("-")];
// see if the localized set is there
if(localizedSet){
// put the bundle in the cache
cache.push("'" + localizedSet.mid + "':function(){" + newline + localizedSet.getText() + newline + "}");
}
}
});
}
}else if(module.internStrings){
pushString(strings, module.internStrings());
}else if(module.getText){
cache.push("'" + p + "':function(){" + newline + module.getText() + newline + "}");
}else{
bc.log("amdMissingLayerModuleText", ["module", module.mid, "layer", resource.mid]);
}
}
}
appendStringsToCache(strings, cache);
// compute the flattened layer bundles (if any)
if(rootBundles.length){
getFlattenedBundles(resource, rootBundles);
// push an *now into the cache that causes the flattened layer bundles to be loaded immediately
cache.push("'*now':function(r){r(['dojo/i18n!*preload*" + getPreloadL10nRootPath(resource.mid) + "*" +
json.stringify(bc.localeList.filter(function(locale){
return !includeLocales || (includeLocales.indexOf(locale) == -1 && locale != "ROOT");
})) + "']);}" + newline);
}
// construct the cache text
if(cache.length && resource.layer.noref){
cache.push("'*noref':1");
}
return (cache.length ? "require({cache:{" + newline + cache.join("," + newline) + "}});" + newline : "") +
(resourceText===undefined ? insertAbsMid(resource.getText(), resource) : (resourceText==false ? "" : resourceText)) +
(resource.layer.postscript ? resource.layer.postscript : "");
},
getStrings = function(
resource
){
var strings = {},
cache = [],
newline = bc.newline;
resource.deps && resource.deps.forEach(function(dep){
if(dep.internStrings){
pushString(strings, dep.internStrings());
}
});
appendStringsToCache(strings, cache);
return cache.length ? "require({cache:{" + newline + cache.join("," + newline) + "}});" + newline : "";
},
getDestFilename = function(
resource
){
if((resource.layer && bc.layerOptimize) || (!resource.layer && bc.optimize)){
return resource.dest + ".uncompressed.js";
}
return resource.dest;
},
processNlsBundle = function(
resource
){
var newline = bc.newline, text, p;
if(resource.localizedSet && resource.bundleValue){
// do a check that all localizations mentioned in the root actually exist
var missing = [];
for(p in resource.bundleValue){
if(p!="root" && resource.bundleValue[p] && !resource.localizedSet[p]){
missing.push("'" + p + "'");
}
}
if(missing.length){
missing.sort();
bc.log("missingL10n", "Root: " + resource.mid + "; missing bundles: " + missing.join(",") + ".");
}
}
if(resource.bundleType=="legacy"){
if(resource.bundleValue){
if(resource.localizedSet){
// this is the root bundle; augment with all available localizations
for(p in resource.localizedSet){
resource.bundleValue[p] = 1;
}
}
text = json.stringify(resource.bundleValue);
}else{
text = "// ERROR: builder was unable to evaluate source bundle; therefore, this empty conversion was written" + newline + "{}";
}
return "define(" + (bc.insertAbsMids ? "'" + resource.mid + "'," : "") + newline + text + newline + ");";
}else{
return insertAbsMid(resource.getText(), resource);
}
},
write = function(
resource,
callback
){
if(resource.layer && (resource.layer.boot || resource.layer.discard)){
// resource.layer.boot layers are written by the writeDojo transform
return 0;
}
var copyright;
if(resource.pack){
copyright = resource.pack.copyrightNonlayers && (resource.pack.copyright || bc.copyright);
}else{
copyright = bc.copyrightNonlayers && bc.copyright;
}
if(!copyright){
copyright = "";
}
var text;
if(resource.tag.nls){
text = processNlsBundle(resource);
}else if(resource.layer){
// don't insertAbsMid or internStrings since that's done in getLayerText
text= getLayerText(resource);
if(resource.layer.compat=="1.6"){
text += "require(" + json.stringify(resource.layer.include) + ");" + bc.newline;
}
copyright = resource.layer.copyright || "";
}else{
text = insertAbsMid(resource.getText(), resource);
if(bc.internStrings){
text = getStrings(resource) + text;
}
}
// remember the uncompressed text for the optimizer
resource.uncompressedText = text;
var destFilename = getDestFilename(resource);
fileUtils.ensureDirectoryByFilename(destFilename);
fs.writeFile(destFilename, bc.newlineFilter(text, resource, "writeAmd"), resource.encoding, function(err){
callback(resource, err);
});
return callback;
};
write.getLayerText = getLayerText;
write.getDestFilename = getDestFilename;
write.computeLayerContents = computeLayerContents;
return write;
});