Skip to content

Commit

Permalink
Fixes #599 - inlined source maps.
Browse files Browse the repository at this point in the history
Enables ingestion of inline, data-uri source maps.
  • Loading branch information
cspotcode authored and jakubpawlowicz committed Jun 23, 2015
1 parent 566b52e commit 46aa071
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 2 deletions.
5 changes: 5 additions & 0 deletions History.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
[3.4.0 / 2015-xx-xx](https:/jakubpawlowicz/clean-css/compare/v3.3.3...master)
==================

* Fixed issue [#599](https:/jakubpawlowicz/clean-css/issues/599) - support for inlined source maps.

[3.3.3 / 2015-06-16](https:/jakubpawlowicz/clean-css/compare/v3.3.2...v3.3.3)
==================

Expand Down
27 changes: 25 additions & 2 deletions lib/utils/input-source-map-tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ var override = require('../utils/object.js').override;

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;
Expand Down Expand Up @@ -63,14 +66,23 @@ function fromSource(self, data, whenDone, context) {
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);
}
}
Expand All @@ -81,6 +93,17 @@ function fromSource(self, data, whenDone, context) {
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) {
Expand Down
52 changes: 52 additions & 0 deletions test/source-map-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var enableDestroy = require('server-destroy');
var port = 24682;

var lineBreak = require('os').EOL;
var escape = global.escape;

vows.describe('source-map')
.addBatch({
Expand Down Expand Up @@ -548,6 +549,13 @@ vows.describe('source-map')
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);');
Expand Down Expand Up @@ -1918,3 +1926,47 @@ vows.describe('source-map')
}
})
.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);
}
};
}

0 comments on commit 46aa071

Please sign in to comment.