Enables ingestion of inline, data-uri source maps.
+[3.4.0 / 2015-xx-xx](https://github.com/jakubpawlowicz/clean-css/compare/v3.3.3...master)
+==================
+
+* Fixed issue [#599](https://github.com/jakubpawlowicz/clean-css/issues/599) - support for inlined source maps.
+
[3.3.3 / 2015-06-16](https://github.com/jakubpawlowicz/clean-css/compare/v3.3.2...v3.3.3)
==================
var MAP_MARKER = /\/\*# sourceMappingURL=(\S+) \*\//;
var REMOTE_RESOURCE = /^(https?:)?\/\//;
+var DATA_URI = /^data:(\S*?)?(;charset=[^;]+)?(;[^,]+?)?,(.+)/;
+
+var unescape = global.unescape;
function InputSourceMapStore(outerContext) {
this.options = outerContext.options;
context.files.pop();
} else if (nextAt == mapMatch.index) {
var isRemote = /^https?:\/\//.test(sourceMapFile) || /^\/\//.test(sourceMapFile);
+ var isDataUri = DATA_URI.test(sourceMapFile);
+
if (isRemote) {
return fetchMapFile(self, sourceMapFile, context, proceedToNext);
} else {
var sourceFile = context.files[context.files.length - 1];
+ var sourceMapPath, sourceMapData;
var sourceDir = sourceFile ? path.dirname(sourceFile) : self.options.relativeTo;
- var sourceMapPath = path.resolve(self.options.root, path.join(sourceDir || '', sourceMapFile));
- var sourceMapData = fs.readFileSync(sourceMapPath, 'utf-8');
+ if (isDataUri) {
+ // source map's path is the same as the source file it comes from
+ sourceMapPath = path.resolve(self.options.root, sourceFile || '');
+ sourceMapData = fromDataUri(sourceMapFile);
+ } else {
+ sourceMapPath = path.resolve(self.options.root, path.join(sourceDir || '', sourceMapFile));
+ sourceMapData = fs.readFileSync(sourceMapPath, 'utf-8');
+ }
self.trackLoaded(sourceFile || undefined, sourceMapPath, sourceMapData);
}
}
return whenDone();
}
+function fromDataUri(uriString) {
+ var match = DATA_URI.exec(uriString);
+ var charset = match[2] ? match[2].split(/[=;]/)[2] : 'us-ascii';
+ var encoding = match[3] ? match[3].split(';')[1] : 'utf8';
+ var data = encoding == 'utf8' ? unescape(match[4]) : match[4];
+
+ var buffer = new Buffer(data, encoding);
+ buffer.charset = charset;
+
+ return buffer.toString();
+}
function fetchMapFile(self, sourceUrl, context, done) {
fetch(self, sourceUrl, function (data) {
var port = 24682;
var lineBreak = require('os').EOL;
+var escape = global.escape;
vows.describe('source-map')
.addBatch({
assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
}
},
+ 'input map as inlined data URI with implicit charset us-ascii, not base64, no content-type': inlineDataUriContext('data:,' + escape(inputMap)),
+ 'input map as inlined data URI with implicit charset us-ascii, base64': inlineDataUriContext('data:application/json;base64,' + new Buffer(inputMap, 'ascii').toString('base64')),
+ 'input map as inlined data URI with implicit charset us-ascii, not base64': inlineDataUriContext('data:application/json,' + escape(inputMap)),
+ 'input map as inlined data URI with charset utf-8, base64': inlineDataUriContext('data:application/json;charset=utf-8;base64,' + new Buffer(inputMap, 'utf8').toString('base64')),
+ 'input map as inlined data URI with charset utf-8, not base64': inlineDataUriContext('data:application/json;charset=utf-8,' + escape(String.fromCharCode.apply(String, new Buffer(inputMap, 'utf8')))),
+ 'input map as inlined data URI with explicit charset us-ascii, base64': inlineDataUriContext('data:application/json;charset=us-ascii;base64,' + new Buffer(inputMap, 'ascii').toString('base64')),
+ 'input map as inlined data URI with explicit charset us-ascii, not base64': inlineDataUriContext('data:application/json;charset=us-ascii,' + escape(inputMap)),
'complex input map': {
'topic': function () {
return new CleanCSS({ sourceMap: true, root: path.dirname(inputMapPath) }).minify('@import url(import.css);');
}
})
.export(module);
+
+ function inlineDataUriContext(dataUri) {
+ return {
+ 'topic': function() {
+ return new CleanCSS({sourceMap: true}).minify('div > a {\n color: red;\n}/*# sourceMappingURL=' + dataUri + ' */');
+ },
+ 'has 3 mappings': function(minified) {
+ assert.lengthOf(minified.sourceMap._mappings._array, 3);
+ },
+ 'has `div > a` mapping': function(minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 0,
+ originalLine: 1,
+ originalColumn: 4,
+ source: 'styles.less',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
+ },
+ 'has `color` mapping': function(minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 6,
+ originalLine: 2,
+ originalColumn: 2,
+ source: 'styles.less',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+ },
+ 'has second `color` mapping': function(minified) {
+ var mapping = {
+ generatedLine: 1,
+ generatedColumn: 12,
+ originalLine: 2,
+ originalColumn: 2,
+ source: 'styles.less',
+ name: null
+ };
+ assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
+ }
+ };
+ }