dojo-util
Version:
Dojo utilities including build system for optimizing JavaScript application performance, and DOH testing tool
197 lines (173 loc) • 7.25 kB
JavaScript
define([
"../buildControl",
"../fileUtils",
"dojo/_base/lang"
], function(bc, fileUtils, lang) {
// note: this transform originated from the v1.6 build system. In 1.8 it has been enhanced to handle
// relative paths for imports when the src and destination tree structures are not predictable.
//
// The cssImportIgnore logic remains although it is of questionable value because it depends on naming a file
// exactly as it is named in an import directive. Instead, the tag "importIgnore" can be used to prevent expanding
// a CSS file and the tag "noOptimize" can be used to prevent optimizing a CSS file. The tag system gives a
// much more general method to handle this problem. Of course cssImportIgnore can be disabled by simply setting
// it to falsy.
var cssImportRegExp = /\@import\s+(url\()?\s*([^);]+)\s*(\))?([\w, ]*)(;)?/g,
cssUrlRegExp = /url\(\s*([^\)]+)\s*\)?/g,
checkSlashes = function(name){
return name.replace(/\\/g, "/");
},
cssImportIgnore = (bc.cssImportIgnore ?
// trim any spaces off of all items; add a trailing comma for fast lookups
bc.cssImportIgnore.split(",").map(function(item){return lang.trim(item);}).join(",") + "," :
""),
cleanCssUrlQuotes = function(url){
// If an URL from a CSS url value contains start/end quotes, remove them.
// This is not done in the regexp, since the CSS spec allows for ' and " in the URL
// which makes the regex overly complicated if they are backslash escaped.
url = lang.trim(url);
if(url.charAt(0) == "'" || url.charAt(0) == '"'){
url = url.substring(1, url.length - 1);
}
return url;
},
removeComments = function(text, filename){
var originalText = text,
startIndex = -1,
endIndex;
while((startIndex = text.indexOf("/*")) != -1){
endIndex = text.indexOf("*/", startIndex + 2);
if(endIndex == -1){
bc.log("cssOptimizeImproperComment", ["CSS file", filename]);
return originalText;
}
text = text.substring(0, startIndex) + text.substring(endIndex + 2, text.length);
}
return text;
},
isRelative = function(url){
var colonIndex = url.indexOf(":");
return url.charAt(0) != "/" && (colonIndex == -1 || colonIndex > url.indexOf("/"));
},
getDestRelativeFilename = function(dest, relativeResource){
var referenceSegments = dest.split("/"),
relativeSegments = relativeResource.dest.split("/");
referenceSegments.pop();
while(referenceSegments.length && relativeSegments.length && referenceSegments[0]==relativeSegments[0]){
referenceSegments.shift();
relativeSegments.shift();
}
for(var i = 0; i<referenceSegments.length; i++){
relativeSegments.unshift("..");
}
return relativeSegments.join("/");
},
getDestRelativeUrlFromSrcUrl = function(dest, fullSrcFilename, msgInfo){
var relativeResource = bc.resources[fullSrcFilename];
if(relativeResource){
return getDestRelativeFilename(dest, relativeResource);
}else{
bc.log("cssOptimizeUnableToResolveURL", msgInfo);
}
return 0;
},
flattenCss = function(resource){
if(resource.optimizedText){
return;
}
var dest = resource.dest,
src = resource.src,
srcReferencePath = fileUtils.getFilepath(checkSlashes(src)),
text = resource.text,
imports = [];
text = removeComments(text, src);
text = text.replace(cssImportRegExp, function(fullMatch, urlStart, importUrl, urlEnd, mediaTypes){
importUrl = checkSlashes(cleanCssUrlQuotes(importUrl));
var fixedRelativeUrl,
fullSrcFilename = (isRelative(importUrl) ? fileUtils.compactPath(fileUtils.catPath(srcReferencePath, importUrl)) : importUrl),
importResource = bc.resources[fullSrcFilename],
ignore = false;
// don't expand if explicitly instructed not to by build control
if(
(cssImportIgnore && cssImportIgnore.indexOf(importUrl + ",") != -1) || // the v1.6- technique
(importResource && importResource.tag.importIgnore) // the v1.8+ technique
){
ignore = true;
bc.log("cssOptimizeIgnored", ["CSS file", src, "import directive", fullMatch]);
}
// don't expand if multiple media types given
if(mediaTypes && lang.trim(mediaTypes)!="all"){
ignore = true;
bc.log("cssOptimizeIgnoredMultiMediaTypes", ["CSS file", src, "import directive", fullMatch]);
}
// even if not expanding, make sure relative urls are adjusted correctly
if(ignore){
if(isRelative(importUrl) && (fixedRelativeUrl = getDestRelativeUrlFromSrcUrl(dest, fullSrcFilename, ["CSS file", src, "import directive", fullMatch]))){
return '@import url("' + fixedRelativeUrl + '")' + (mediaTypes || "") + ";";
}else{
// right or wrong, this is our only choice at this point
return fullMatch;
}
}
// at this point, we want to expand the import into the calling resource
if(!importResource){
bc.log("cssOptimizeIgnoredNoResource", ["CSS file", src, "import directive", fullMatch]);
return fullMatch;
}
flattenCss(importResource);
var importContents = importResource.optimizedText;
// since importContents is flattened, all relative urls are computed with respect to the destination location of importResource
// these urls need to be adjusted with respect to resource
var importResourceDestPath = fileUtils.getFilepath(importResource.dest);
return importContents.replace(cssUrlRegExp, function(fullMatch, urlMatch){
var fixedUrl = checkSlashes(cleanCssUrlQuotes(urlMatch)),
queryString = "",
queryStart = fixedUrl.search(/[?#]/);
if(queryStart > 0){
queryString = fixedUrl.slice(queryStart);
fixedUrl = fixedUrl.slice(0, queryStart);
}
if(isRelative(fixedUrl)){
var fullDestFilename = fileUtils.compactPath(fileUtils.catPath(importResourceDestPath, fixedUrl)),
relativeResource = bc.resourcesByDest[fullDestFilename];
if(!relativeResource){
bc.log("cssOptimizeUnableToResolveURL", ["CSS file", src, "import", importResource.src, "relative URL", fullMatch]);
}else{
return 'url("' + getDestRelativeFilename(resource.dest, relativeResource) + queryString + '")';
}
}
// right or wrong, this is our only choice at this point
return fullMatch;
});
});
//Hoist imports to maintain valid CSS
text = text.replace(cssImportRegExp, function(fullMatch){
imports.push(fullMatch);
return "";
});
if(imports.length) {
text = imports.join("\n") + "\n" + text;
}
if(/keepLines/i.test(bc.cssOptimize)){
// remove multiple empty lines.
text = text.replace(/(\r\n)+/g, "\r\n").replace(/\n+/g, "\n");
}else{
// remove newlines and extra spaces
text = text.replace(/[\r\n]/g, "").replace(/\s+/g, " ").replace(/\{\s/g, "{").replace(/\s\}/g, "}");
}
resource.optimizedText = text;
if(!resource.tag.noOptimize){
resource.rawText = resource.text;
resource.text = text;
}
};
return function(resource, callback) {
try{
if(bc.cssOptimize && !resource.tag.noOptimize){
flattenCss(resource);
bc.log("cssOptimize", ["file", resource.src]);
}
}catch(e){
bc.log("cssOptimizeFailed", ["file", resource.src, "error", e]);
}
};
});