From 46aa07136f6b1da302e647c15ee17ba8a3fec444 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Fri, 12 Jun 2015 15:47:16 -0400 Subject: [PATCH] Fixes #599 - inlined source maps. Enables ingestion of inline, data-uri source maps. --- History.md | 5 +++ lib/utils/input-source-map-tracker.js | 27 ++++++++++++-- test/source-map-test.js | 52 +++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 17a94aec..56f54f89 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +[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) ================== diff --git a/lib/utils/input-source-map-tracker.js b/lib/utils/input-source-map-tracker.js index f74e6649..7db9eb9e 100644 --- a/lib/utils/input-source-map-tracker.js +++ b/lib/utils/input-source-map-tracker.js @@ -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; @@ -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); } } @@ -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) { diff --git a/test/source-map-test.js b/test/source-map-test.js index 7f474c9c..c4a1ac0f 100644 --- a/test/source-map-test.js +++ b/test/source-map-test.js @@ -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({ @@ -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);'); @@ -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); + } + }; + } -- 2.34.1