Permalink
Browse files

* Added node 0.4+ requirement.

* Added cryptedStamp option for renaming image files with MD5 hash attached (hard cache boosters).
  • Loading branch information...
1 parent c3f3c13 commit abb12ae5962546e742dd4ed7396f579c3cd8fc8e @GoalSmashers GoalSmashers committed Jul 3, 2012
Showing with 211 additions and 65 deletions.
  1. +1 −1 .gitignore
  2. +22 −8 bin/enhancecss
  3. +64 −37 lib/enhance.js
  4. +3 −0 package.json
  5. +43 −19 test/binary-test.js
  6. +78 −0 test/embed-test.js
View
@@ -1,2 +1,2 @@
node_modules
-
+.DS_Store
View
@@ -1,29 +1,39 @@
#!/usr/bin/env node
-global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
var argv = require('optimist').argv,
- enhanceCss = require('../index'),
- fs = require('fs');
+ EnhanceCSS = require('../index'),
+ fs = require('fs'),
+ path = require('path'),
+ util = require('util');
var options = {
source: null,
target: null,
rootPath: null,
assetHosts: null,
pregzip: false,
- noEmbed: false
+ noEmbed: false,
+ cryptedStamp: false
};
+var fromStdin = !process.env['__DIRECT__'] && process.stdin.readable;
if (argv.noembedversion) options.noEmbed = true;
if (argv.assethosts) options.assetHosts = argv.assethosts;
if (argv.pregzip) options.pregzip = true;
+if (argv.cryptedstamp) options.cryptedStamp = true;
if (argv.o) options.target = argv.o;
if (argv._) options.source = argv._[0];
options.rootPath = argv.root || process.cwd();
-if (argv.h || argv.help) {
- global.sys.print('Usage: enhancecss -o <output-file> --noembedversion --assethosts <host-pattern> --root <root-path> --pregzip <input-file>\n');
+if (argv.v) {
+ var packageConfig = fs.readFileSync(path.join(path.dirname(fs.realpathSync(process.argv[1])), '../package.json'));
+ util.puts(JSON.parse(packageConfig).version);
+ process.exit(0);
+}
+
+if (argv.h || argv.help || (!fromStdin && argv._.length == 0)) {
+ util.print('usage: enhancecss -o <output-file> [--cryptedstamp] [--noembedversion] [--assethosts <host-pattern>] [--root <root-path>] [--pregzip <input-file>]\n');
process.exit(0);
}
@@ -41,15 +51,19 @@ if (options.source) {
}
function enhance(source) {
- return new enhanceCss({ rootPath: options.rootPath, assetHosts: options.assetHosts, noEmbedVersion: options.noEmbed }).process(source);
+ return new EnhanceCSS({ rootPath: options.rootPath,
+ assetHosts: options.assetHosts,
+ noEmbedVersion: options.noEmbed,
+ cryptedStamp: options.cryptedStamp
+ }).process(source);
}
function write(target, content) {
if (typeof target == "string") {
targetFile = fs.createWriteStream(target, { flags: 'w', encoding: 'utf-8', mode: 0644 });
targetFile.write(content);
targetFile.end();
-
+
if (options.pregzip) {
gzippedTargetFile = fs.createWriteStream(target + '.gz', { flags: 'w', encoding: 'utf-8', mode: 0644 });
gzippedTargetFile.write(content.compressed);
View
@@ -1,11 +1,11 @@
var fs = require('fs'),
path = require('path'),
gzip = require('gzip'),
+ crypto = require('crypto'),
querystring = require('querystring');
var EnhanceCSS = function(options) {
this.options = options;
- this.hostsCycleCycle = null;
};
EnhanceCSS.prototype = {
@@ -14,74 +14,83 @@ EnhanceCSS.prototype = {
var self = this,
missing = {},
data = { original: css },
- allUrls = {};
-
+ embedUrls = {},
+ allUrls = [];
+
// Only to find duplicates
(css.match(this.urlPattern) || []).forEach(function(url) {
var pathInfo = self.parseImageUrl(url.substring(4, url.length - 1));
-
+
if (pathInfo.query.embed !== undefined) {
- if (allUrls[pathInfo.relative])
- allUrls[pathInfo.relative]++;
+ if (embedUrls[pathInfo.relative])
+ embedUrls[pathInfo.relative]++;
else
- allUrls[pathInfo.relative] = 1;
+ embedUrls[pathInfo.relative] = 1;
}
+
+ allUrls.push(pathInfo);
});
-
+
// Get embedded version
data.embedded = {};
data.embedded.plain = css.replace(this.urlPattern, function(match, url) {
var pathInfo = self.parseImageUrl(url);
-
+
// Break early if file does not exist
if (!pathInfo.exists) {
missing[pathInfo.relative] = 1;
return match;
}
-
+
// Break unless ?embed param or there's more than one such image
- if (pathInfo.query.embed === undefined || allUrls[pathInfo.relative] > 1) {
- var mtime = Date.parse(fs.statSync(pathInfo.absolute).mtime) / 1000;
+ if (pathInfo.query.embed === undefined || embedUrls[pathInfo.relative] > 1) {
var assetHost = self.nextAssetHost();
- return ['url(', (assetHost == null ? '' : '//' + assetHost), pathInfo.relative, '?', mtime, ')'].join('');
+ self._addFileStamp(pathInfo);
+ return ['url(', (assetHost == null ? '' : '//' + assetHost), pathInfo.relative, ')'].join('');
}
-
+
var type = path.extname(pathInfo.relative).substring(1);
if (type == 'jpg') type = 'jpeg';
if (type == 'svg') type = 'svg+xml';
// Break unless unsupported type
if (!/(jpeg|gif|png|svg\+xml)/.test(type)) return match;
-
+
var base64 = fs.readFileSync(pathInfo.absolute).toString('base64');
-
+
return "url(data:image/" + type + ";base64," + base64 + ")";
});
-
+
// Get not embedded version (aka <= IE7)
if (this.options.noEmbedVersion) {
data.notEmbedded = {};
data.notEmbedded.plain = css.replace(this.urlPattern, function(match, url) {
var pathInfo = self.parseImageUrl(url);
-
+
// Break early if file does not exist
if (!pathInfo.exists) return match;
-
- var mtime = Date.parse(fs.statSync(pathInfo.absolute).mtime) / 1000;
+
var assetHost = self.nextAssetHost();
- return ['url(', (assetHost == null ? '' : '//' + assetHost), pathInfo.relative, '?', mtime, ')'].join('');
+ self._addFileStamp(pathInfo);
+ return ['url(', (assetHost == null ? '' : '//' + assetHost), pathInfo.relative, ')'].join('');
+ });
+ }
+
+ if (this.options.cryptedStamp) {
+ allUrls.forEach(function(pathInfo) {
+ self._addFileStamp(pathInfo);
});
}
-
+
// Update missing & duplicates lists
data.missing = Object.keys(missing);
data.duplicates = [];
- for (var key in allUrls) {
- if (hasOwnProperty.call(allUrls, key)) {
- if (allUrls[key] > 1) data.duplicates.push(key);
+ for (var key in embedUrls) {
+ if (hasOwnProperty.call(embedUrls, key)) {
+ if (embedUrls[key] > 1) data.duplicates.push(key);
}
}
-
+
// Create gzipped version too if requested
if (this.options.pregzip) {
var count = this.options.noEmbedVersion ? 2 : 1;
@@ -98,7 +107,7 @@ EnhanceCSS.prototype = {
if (--count == 0) callback(null, data);
});
};
-
+
var dataProcessor = function(type) {
return function(compressedBuffers, outputLength) {
data[type].compressed = new Buffer(outputLength);
@@ -110,36 +119,36 @@ EnhanceCSS.prototype = {
});
};
};
-
+
compress('embedded');
if (this.options.noEmbedVersion) compress('notEmbedded');
-
+
return;
}
-
+
if (callback) callback(null, data);
else return data;
},
-
+
parseImageUrl: function(url) {
var tokens = url.replace(/['"]/g, '').split('?');
var query = tokens[1] ? querystring.parse(tokens[1]) : {};
var absolutePath = path.normalize(this.options.rootPath + tokens[0]);
var imagePath = absolutePath.substring(this.options.rootPath.length);
-
+
return {
relative: imagePath,
absolute: absolutePath,
query: query,
exists: path.existsSync(absolutePath)
};
},
-
+
nextAssetHost: function() {
var hosts = this.options.assetHosts;
if (!hosts) return null;
if (hosts.indexOf('[') == -1) return hosts;
-
+
if (!this.hostsCycle) {
this.hostsCycle = {
next: function() {
@@ -148,22 +157,40 @@ EnhanceCSS.prototype = {
var start = hosts.indexOf('[');
var end = hosts.indexOf(']');
var pattern = hosts.substring(start + 1, end);
-
+
pattern.split(',').forEach(function(version) {
cycleList.push(hosts.replace(/\[([^\]])+\]/, version));
});
-
+
this.cycleList = cycleList;
this.index = 0;
}
-
+
if (this.index == this.cycleList.length) this.index = 0;
return this.cycleList[this.index++];
}
};
}
return this.hostsCycle.next();
+ },
+
+ _addFileStamp: function(pathInfo) {
+ if (this.options.cryptedStamp && (pathInfo.query.embed === undefined || this.options.noEmbedVersion)) {
+ var source = fs.readFileSync(pathInfo.absolute),
+ encrypted = crypto.createHash('md5');
+
+ encrypted.update(source.toString('utf8'));
+ var stamp = encrypted.digest('hex'),
+ targetPath = pathInfo.absolute.replace(/\./, '-' + stamp + '.');
+
+ if (!path.existsSync(targetPath))
+ fs.writeFileSync(targetPath, source);
+
+ pathInfo.relative = pathInfo.relative.replace(/\./, '-' + stamp + '.');
+ } else {
+ pathInfo.relative += "?" + Date.parse(fs.statSync(pathInfo.absolute).mtime) / 1000;
+ }
}
};
View
@@ -16,5 +16,8 @@
"dependencies": {
"optimist": "0.1.x",
"gzip": "0.1.x"
+ },
+ "engines": {
+ "node": ">=0.4.0"
}
}
Oops, something went wrong. Retry.

0 comments on commit abb12ae

Please sign in to comment.