Adds basic support for input source maps.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Tue, 11 Nov 2014 18:41:54 +0000 (18:41 +0000)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Mon, 8 Dec 2014 09:39:15 +0000 (09:39 +0000)
* `sourceMap` option can be given as source map string.
* Particularly useful for preprocessed stylesheets.

README.md
lib/clean.js
lib/selectors/source-map-stringifier.js
lib/selectors/stringifier.js
test/source-map-test.js

index 4705230..248c14b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -152,6 +152,7 @@ CleanCSS constructor accepts a hash as a parameter, i.e.,
 * `roundingPrecision` - rounding precision; defaults to `2`; `-1` disables rounding
 * `shorthandCompacting` - set to false to skip shorthand compacting (default is true unless sourceMap is set when it's false)
 * `sourceMap` - exposes source map under `sourceMap` property, e.g. `new CleanCSS().minify(source).sourceMap` (default is false)
+  If input styles are a product of CSS preprocessor (LESS, SASS) an input source map can be passed as a string.
 * `target` - path to a folder or an output file to which __rebase__ all URLs
 
 ### How to use clean-css with build tools?
index babb0f2..d0c1f9d 100644 (file)
@@ -36,7 +36,7 @@ var CleanCSS = module.exports = function CleanCSS(options) {
     root: options.root,
     roundingPrecision: options.roundingPrecision,
     shorthandCompacting: !!options.sourceMap ? false : (undefined === options.shorthandCompacting ? true : !!options.shorthandCompacting),
-    sourceMap: !!options.sourceMap,
+    sourceMap: options.sourceMap,
     target: options.target
   };
 
@@ -140,7 +140,7 @@ function minify(data) {
   run(freeTextProcessor, 'escape');
 
   run(function() {
-    var stringifier = new stringifierClass(options.keepBreaks, function (data) {
+    var stringifier = new stringifierClass(options, function (data) {
       data = freeTextProcessor.restore(data);
       data = urlsProcessor.restore(data);
       data = options.rebase ? urlRebase.process(data) : data;
index ea18539..86e4603 100644 (file)
@@ -1,11 +1,15 @@
+var SourceMapConsumer = require('source-map').SourceMapConsumer;
 var SourceMapGenerator = require('source-map').SourceMapGenerator;
 
 var lineBreak = require('os').EOL;
 
-function SourceMapStringifier(keepBreaks, restoreCallback) {
-  this.keepBreaks = keepBreaks;
+function SourceMapStringifier(options, restoreCallback) {
+  this.keepBreaks = options.keepBreaks;
   this.restoreCallback = restoreCallback;
-  this.sourceMap = new SourceMapGenerator();
+  this.outputMap = new SourceMapGenerator();
+  this.inputMap = typeof options.sourceMap == 'string' ?
+    new SourceMapConsumer(options.sourceMap) :
+    null;
 }
 
 function valueRebuilder(list, store, separator) {
@@ -53,15 +57,19 @@ function rebuild(tokens, store, keepBreaks, isFlatBlock) {
 
 function track(context, value, metadata) {
   if (metadata) {
-    context.sourceMap.addMapping({
+    var original = context.inputMap ?
+      context.inputMap.originalPositionFor(metadata) :
+      {};
+
+    context.outputMap.addMapping({
       generated: {
         line: context.line,
         column: context.column,
       },
-      source: metadata.source || '__stdin__.css',
+      source: original.source || metadata.source || '__stdin__.css',
       original: {
-        line: metadata.line,
-        column: metadata.column
+        line: original.line || metadata.line,
+        column: original.column || metadata.column
       },
       name: value
     });
@@ -78,7 +86,8 @@ SourceMapStringifier.prototype.toString = function (tokens) {
   var context = {
     column: 1,
     line: 1,
-    sourceMap: this.sourceMap
+    inputMap: this.inputMap,
+    outputMap: this.outputMap
   };
 
   function store(token) {
@@ -95,7 +104,7 @@ SourceMapStringifier.prototype.toString = function (tokens) {
   rebuild(tokens, store, this.keepBreaks, false);
 
   return {
-    sourceMap: this.sourceMap,
+    sourceMap: this.outputMap,
     styles: output.join('').trim()
   };
 };
index fcbc3d9..685a0cf 100644 (file)
@@ -1,7 +1,7 @@
 var lineBreak = require('os').EOL;
 
-function Stringifier(keepBreaks, restoreCallback) {
-  this.keepBreaks = keepBreaks;
+function Stringifier(options, restoreCallback) {
+  this.keepBreaks = options.keepBreaks;
   this.restoreCallback = restoreCallback;
 }
 
index 3116969..8d94c98 100644 (file)
@@ -2,6 +2,8 @@ var vows = require('vows');
 var assert = require('assert');
 var CleanCSS = require('../index');
 
+var inputSourceMap = '{"version":3,"sources":["styles.less"],"names":[],"mappings":"AAAA,GACE;EACE,UAAA","file":"styles.css"}';
+
 vows.describe('source-map')
   .addBatch({
     'module #1': {
@@ -194,4 +196,34 @@ vows.describe('source-map')
       }
     },
   })
+  .addBatch({
+    'input map as string': {
+      'topic': new CleanCSS({ sourceMap: inputSourceMap }).minify('div > a {\n  color: red;\n}'),
+      'should have 2 mappings': function (minified) {
+        assert.equal(2, minified.sourceMap._mappings.length);
+      },
+      'should have selector mapping': function (minified) {
+        var mapping = {
+          generatedLine: 1,
+          generatedColumn: 1,
+          originalLine: 1,
+          originalColumn: 1,
+          source: 'styles.less',
+          name: 'div>a'
+        };
+        assert.deepEqual(mapping, minified.sourceMap._mappings[0]);
+      },
+      'should have _color:red_ mapping': function (minified) {
+        var mapping = {
+          generatedLine: 1,
+          generatedColumn: 7,
+          originalLine: 3,
+          originalColumn: 4,
+          source: 'styles.less',
+          name: 'color:red'
+        };
+        assert.deepEqual(mapping, minified.sourceMap._mappings[1]);
+      }
+    }
+  })
   .export(module);