Fixes #419 - handling multiple input source maps.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Tue, 27 Jan 2015 08:16:38 +0000 (08:16 +0000)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Thu, 29 Jan 2015 22:40:42 +0000 (22:40 +0000)
Adds support for multiple input source maps when data is passed in
as a hash, e.g.:

```
new CleanCSS({ sourceMap: true }).minify({
  'path/to/source/1': {
    styles: '...styles...',
    sourceMap: '...source-map...'
  },
  'path/to/source/2': {
    styles: '...styles...',
    sourceMap: '...source-map...'
  }
})
```

Effectively it is the same as concatenating source maps, but should
be less error-prone.

See 'multiple source maps' in `test/source-map-test.js` for examples.

History.md
README.md
lib/clean.js
lib/utils/input-source-map-tracker.js
lib/utils/source-reader.js
test/source-map-test.js

index e5a2635..6fac3ec 100644 (file)
@@ -8,6 +8,7 @@
 * Fixed issue [#351](https://github.com/GoalSmashers/clean-css/issues/351) - remote `@import`s after content.
 * Fixed issue [#357](https://github.com/GoalSmashers/clean-css/issues/357) - non-standard but valid URLs.
 * Fixed issue [#416](https://github.com/GoalSmashers/clean-css/issues/416) - accepts hash as `minify` argument.
+* Fixed issue [#419](https://github.com/GoalSmashers/clean-css/issues/419) - multiple input source maps.
 * Fixed issue [#435](https://github.com/GoalSmashers/clean-css/issues/435) - `background-clip` in shorthand.
 * Fixed issue [#439](https://github.com/GoalSmashers/clean-css/issues/439) - `background-origin` in shorthand.
 * Fixed issue [#442](https://github.com/GoalSmashers/clean-css/issues/442) - space before adjacent `nav`.
index 3dabed0..3eae040 100644 (file)
--- a/README.md
+++ b/README.md
@@ -242,6 +242,23 @@ new CleanCSS({ sourceMap: inputSourceMapAsString, target: pathToOutputDirectory
 });
 ```
 
+Or even multiple input source maps at once (available since version 3.1):
+
+```javascript
+new CleanCSS({ sourceMap: true, target: pathToOutputDirectory }).minify({
+  'path/to/source/1': {
+    styles: '...styles...',
+    sourceMap: '...source-map...'
+  },
+  'path/to/source/2': {
+    styles: '...styles...',
+    sourceMap: '...source-map...'
+  }
+}, function (minified) {
+  // access minified.sourceMap as above
+});
+```
+
 #### Caveats
 
 * Shorthand compacting is currently disabled when source maps are enabled, see [#399](https://github.com/GoalSmashers/clean-css/issues/399)
index 751c8bd..c84beb2 100644 (file)
@@ -59,6 +59,9 @@ CleanCSS.prototype.minify = function(data, callback) {
     sourceTracker: new SourceTracker()
   };
 
+  if (context.options.sourceMap)
+    context.inputSourceMapTracker = new InputSourceMapTracker(context);
+
   data = new SourceReader(context, data).toString();
 
   if (context.options.processImport || data.indexOf('@shallow') > 0) {
@@ -92,7 +95,6 @@ function runMinifier(callback, context) {
 
   return function (data) {
     if (context.options.sourceMap) {
-      context.inputSourceMapTracker = new InputSourceMapTracker(context);
       return context.inputSourceMapTracker.track(data, function () { return whenSourceMapReady(data); });
     } else {
       return whenSourceMapReady(data);
index 4a83c4b..31eda87 100644 (file)
@@ -137,6 +137,10 @@ InputSourceMapStore.prototype.track = function (data, whenDone) {
     fromSource(this, data, whenDone, { files: [], cursor: 0, errors: this.errors });
 };
 
+InputSourceMapStore.prototype.trackLoaded = function (sourceFile, sourceMap) {
+  this.maps[sourceFile] = new SourceMapConsumer(sourceMap);
+};
+
 InputSourceMapStore.prototype.isTracking = function (sourceInfo) {
   return !!this.maps[sourceInfo.source];
 };
index 4bc31f3..d6b9bf1 100644 (file)
@@ -39,6 +39,8 @@ function fromHash(outerContext, sources) {
 
   for (var source in sources) {
     var styles = sources[source].styles;
+    var inputSourceMap = sources[source].sourceMap;
+
     var rewriter = new UrlRewriter({
       absolute: !!outerContext.options.root,
       relative: !outerContext.options.root,
@@ -47,8 +49,14 @@ function fromHash(outerContext, sources) {
       fromBase: path.dirname(path.resolve(source)),
       toBase: toBase
     });
-
     styles = rewriter.process(styles);
+
+    if (outerContext.options.sourceMap && inputSourceMap) {
+      var absoluteSource = path.resolve(source);
+      styles = outerContext.sourceTracker.store(absoluteSource, styles);
+      outerContext.inputSourceMapTracker.trackLoaded(absoluteSource, inputSourceMap);
+    }
+
     data.push(styles);
   }
 
index 4e06678..0652d13 100644 (file)
@@ -783,4 +783,119 @@ vows.describe('source-map')
       }
     }
   })
+  .addBatch({
+    'multiple source maps': {
+      'relative to local': {
+        'topic': new CleanCSS({ sourceMap: true }).minify({
+          'test/fixtures/source-maps/some.css': {
+            styles: 'div {\n  color: red;\n}',
+            sourceMap: '{"version":3,"sources":["some.less"],"names":[],"mappings":"AAAA;EACE,UAAA","file":"some.css"}'
+          },
+          'test/fixtures/source-maps/styles.css': {
+            styles: 'div > a {\n  color: blue;\n}',
+            sourceMap: '{"version":3,"sources":["styles.less"],"names":[],"mappings":"AAAA,GAAI;EACF,WAAA","file":"styles.css"}'
+          },
+          'test/fixtures/source-maps/nested/once.css': {
+            styles: 'section > div a {\n  color: red;\n}',
+            sourceMap: '{"version":3,"sources":["once.less"],"names":[],"mappings":"AAAA,OACE,MAAM;EACJ,UAAA","file":"once.css"}'
+          }
+        }),
+        'has right output': function (errors, minified) {
+          assert.equal(minified.styles, 'div,section>div a{color:red}div>a{color:#00f}');
+        },
+        'should have 5 mappings': function (minified) {
+          assert.lengthOf(minified.sourceMap._mappings._array, 5);
+        },
+        'should have "div" mapping': function (minified) {
+          var mapping = {
+            generatedLine: 1,
+            generatedColumn: 0,
+            originalLine: 1,
+            originalColumn: 0,
+            source: 'some.less',
+            name: null
+          };
+          assert.deepEqual(minified.sourceMap._mappings._array[0], mapping);
+        },
+        'should have "section > div a" mapping': function (minified) {
+          var mapping = {
+            generatedLine: 1,
+            generatedColumn: 4,
+            originalLine: 2,
+            originalColumn: 8,
+            source: 'once.less',
+            name: null
+          };
+          assert.deepEqual(minified.sourceMap._mappings._array[1], mapping);
+        },
+        'should have "color: red" mapping': function (minified) {
+          var mapping = {
+            generatedLine: 1,
+            generatedColumn: 18,
+            originalLine: 2,
+            originalColumn: 2,
+            source: 'some.less',
+            name: null
+          };
+          assert.deepEqual(minified.sourceMap._mappings._array[2], mapping);
+        },
+        'should have "div > a" mapping': function (minified) {
+          var mapping = {
+            generatedLine: 1,
+            generatedColumn: 28,
+            originalLine: 1,
+            originalColumn: 4,
+            source: 'styles.less',
+            name: null
+          };
+          assert.deepEqual(minified.sourceMap._mappings._array[3], mapping);
+        },
+        'should have "color: blue" mapping': function (minified) {
+          var mapping = {
+            generatedLine: 1,
+            generatedColumn: 34,
+            originalLine: 2,
+            originalColumn: 2,
+            source: 'styles.less',
+            name: null
+          };
+          assert.deepEqual(minified.sourceMap._mappings._array[4], mapping);
+        }
+      }
+    },
+    'relative to path': {
+      'complex but partial input map referenced by path': {
+        'topic': new CleanCSS({ sourceMap: true, target: process.cwd() }).minify({
+          'test/fixtures/source-maps/some.css': {
+            styles: 'div {\n  color: red;\n}',
+            sourceMap: '{"version":3,"sources":["some.less"],"names":[],"mappings":"AAAA;EACE,UAAA","file":"some.css"}'
+          },
+          'test/fixtures/source-maps/styles.css': {
+            styles: 'div > a {\n  color: blue;\n}',
+            sourceMap: '{"version":3,"sources":["styles.less"],"names":[],"mappings":"AAAA,GAAI;EACF,WAAA","file":"styles.css"}'
+          },
+          'test/fixtures/source-maps/nested/once.css': {
+            styles: 'section > div a {\n  color: red;\n}',
+            sourceMap: '{"version":3,"sources":["once.less"],"names":[],"mappings":"AAAA,OACE,MAAM;EACJ,UAAA","file":"once.css"}'
+          }
+        }),
+        'should have 5 mappings': function (minified) {
+          assert.lengthOf(minified.sourceMap._mappings._array, 5);
+        },
+        'should have right sources': function (minified) {
+          var sources = [];
+          minified.sourceMap._mappings._array.forEach(function (m) {
+            if (sources.indexOf(m.source) === -1)
+              sources.push(m.source);
+          });
+
+          assert.deepEqual(sources, [
+            'test/fixtures/source-maps/some.less',
+            'test/fixtures/source-maps/nested/once.less',
+            'test/fixtures/source-maps/styles.less'
+          ]);
+        }
+      }
+    }
+  })
   .export(module);