Skip to content

Commit

Permalink
Refactor url to support multiple resolutions
Browse files Browse the repository at this point in the history
  • Loading branch information
kpdecker committed Nov 18, 2012
1 parent 9a809d8 commit d562af0
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 70 deletions.
2 changes: 1 addition & 1 deletion lib/url-scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Scanner.prototype.generateSelector = function(tree) {
};

Scanner.prototype.visitLiteral = function(lit) {
if (lit.url && this.imagesSeen[lit.url.href] > 1) {
if (lit.url && (this.imagesSeen[lit.url] || {}).count > 1) {
this.dupeUrl = lit.url;
}
};
Expand Down
204 changes: 135 additions & 69 deletions lib/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ var mimes = {
module.exports = function(options) {
options = options || {};

var sizeLimit = options.limit || 30000
, _paths = options.paths || [];
var _paths = options.paths || [],
imageCount = 0;

function url(url) {
// Inject the merge filter. This executes after the AST as been finalized and will
Expand All @@ -76,88 +76,154 @@ module.exports = function(options) {

// Parse literal
var url = parse(url)
, ext = extname(url.pathname)
, mime = mimes[ext]
, literal = new nodes.Literal('url("' + url.href + '")')
, paths = _paths.concat(this.paths)
, found
, buf;
, mime = mimes[extname(url.pathname)];

this.imagesSeen[url.href] = (this.imagesSeen[url.href] || 0) + 1;

// Not supported
if (!mime) return literal;
// Not supported MIME or absolute URL
if (!mime || url.protocol) {
return literalNode(url.href);
}

// Absolute
if (url.protocol) return literal;
var imageInfo = this.imagesSeen[url.href] = (this.imagesSeen[url.href] || {count: 0, id: imageCount++});
imageInfo.count++;
if (imageInfo.literal) {
return imageInfo.literal.clone();
}

// Lookup
if (options.res) {
var ext = extname(url.pathname)
, highResFileName = basename(url.pathname, ext) + '@' + options.res + 'x' + ext
, highResPath = dirname(url.pathname) + '/' + highResFileName;
found = utils.lookup(highResPath, paths);

if (found) {
// Reset our literal path for the file too large case
var dir = dirname(url.href) + '/';
if (dir === './') {
dir = '';
}
var lookup = imageInfo.resolutions = lookupPath(url, _paths.concat(this.paths), options);

if (!lookup.length) {
// No resolutions defined
return imageInfo.literal = literalNode(lookup.href);
} else if (lookup.length === 1) {
// Only one output
return imageInfo.literal = outputPath(lookup[0], url.href, options);
} else {
// Multiple outputs here, add a placeholder that we can replace on output
return imageInfo.literal = literalNode('stylusImages:https://' + imageInfo.id);
}
}

url.raw = true;
return url;
};

url.href = dir + highResFileName;
literal = new nodes.Literal('url("' + url.href + '")');
function literalNode(href) {
return new nodes.Literal('url("' + href + '")');
}

function lookupPath(url, paths, options) {
var resolutions = options.resolutions || [0],
dir = dirname(url.pathname),
ext = extname(url.pathname),
base = basename(url.pathname, ext);

if (options.res) {
resolutions = [options.res];
}

// Zero is a special case for the base file and 1x
if (resolutions.indexOf(0) === -1) {
resolutions = resolutions.slice();
resolutions.unshift(0);
}

resolutions = resolutions.map(function(resolution) {
var href = url.href,
highResFileName = base + (resolution ? '@' + resolution + 'x' : '') + ext,
highResPath = dir + '/' + highResFileName,

found = utils.lookup(highResPath, paths);

if (found) {
// Reset our literal path for the file too large case
var urlDir = dirname(href) + '/';
if (urlDir === './') {
urlDir = '';
}
}
if (!found) {
found = utils.lookup(url.pathname, paths);

href = urlDir + highResFileName;
}

// Remap relative paths
if (options.externalPrefix && !/^\//.test(url.href)) {
literal = new nodes.Literal('url("' + options.externalPrefix + url.href + '")');
if (options.externalPrefix && !/^\//.test(href)) {
href = options.externalPrefix + href;
}

// Failed to lookup
if (!found) return literal;

// Read data
buf = fs.readFileSync(found);

// To large
if (buf.length > sizeLimit) {
// If copy files flag is true and not an absolute url
if (options.copyFiles && !/^\//.test(url.href)) {
var relativeDir = dirname(url.pathname),
outputDir = options.outdir || dirname(this.filename),
components = relativeDir.split('/');

for (var i = 0; i < components.length; i++) {
try {
outputDir += '/' + components[i];
fs.mkdirSync(outputDir, parseInt('0777', 8));
} catch (err) {
if (err.code !== 'EEXIST') {
throw err;
}
return {
resolution: parseFloat(resolution),
found: found,
href: href
};
});

// Sort so we always have the lowest value providing the default
resolutions = resolutions.sort(function(a, b) {
return a.resolution - b.resolution;
});

// If an explicit resolution was requested return only that
if (options.res) {
resolutions = [selectResolution(options.res, resolutions)];
}

return resolutions;
}

function selectResolution(resolution, lookup) {
return lookup.reduceRight(function(prev, current) {
if ((Math.abs(prev.resolution - resolution) > Math.abs(current.resolution - resolution))
|| !prev.found) {
return current;
} else {
return prev;
}
});
}

function outputPath(resolution, id, options) {
if (!resolution.found) {
return literalNode(resolution.href);
}

var buf = fs.readFileSync(resolution.found),
sizeLimit = options.limit || 30000;

// To large
if (buf.length > sizeLimit) {
// If copy files flag is true and not an absolute url
if (options.copyFiles && !/^\//.test(resolution.href)) {
var relativeDir = dirname(resolution.href),
// TODO : Do something about this.filename reference
outputDir = options.outdir || dirname(this.filename),
components = relativeDir.split('/');

for (var i = 0; i < components.length; i++) {
try {
outputDir += '/' + components[i];
fs.mkdirSync(outputDir, parseInt('0777', 8));
} catch (err) {
if (err.code !== 'EEXIST') {
throw err;
}
}
fs.writeFileSync(outputDir + '/' + basename(url.href), buf);
}
return literal;
fs.writeFileSync(outputDir + '/' + basename(resolution.href), buf);
}

// Encode
var ret = new nodes.Literal('url("data:' + mime + ';base64,' + buf.toString('base64') + '")');
ret.url = url;
ret.clone = function() {
var ret = nodes.Literal.prototype.clone.call(this);
ret.url = url;
return ret;
};
return literalNode(resolution.href);
}

// Encode
var ret = literalNode('data:' + mimes[extname(resolution.href)] + ';base64,' + buf.toString('base64'));
ret.url = id;
ret.clone = function() {
var ret = nodes.Literal.prototype.clone.call(this);
ret.url = this.url;
return ret;
};
return ret;
}

url.raw = true;
return url;
};
module.exports.lookup = lookupPath;
module.exports.selectResolution = selectResolution;
module.exports.output = outputPath;

0 comments on commit d562af0

Please sign in to comment.