* Supporting both relative and absolute paths (via `-r` / `root` options).
and attributes.
* Fixed issue [#44](https://github.com/GoalSmashers/clean-css/issues/44) - examples in --help.
* Fixed issue [#83](https://github.com/GoalSmashers/clean-css/issues/83) - HSL to hex color conversions.
+* Fixed issue [#2](https://github.com/GoalSmashers/clean-css/issues/2) - resolving @import rules.
0.10.2 / 2013-03-19
==================
* `-b`, `--keep-line-breaks` Keep line breaks
* `--s0` Remove all special comments (i.e. `/*! special comment */`)
* `--s1` Remove all special comments but the first one
+* `-r`, `--root [root-path]` Set a root path to which resolve absolute @import rules
* `-o`, `--output [output-file]` Use [output-file] as output instead of stdout
#### Examples:
.option('-b, --keep-line-breaks', 'Keep line breaks')
.option('--s0', 'Remove all special comments (i.e. /*! special comment */)')
.option('--s1', 'Remove all special comments but the first one')
+ .option('-r, --root [root-path]', 'Set a root path to which resolve absolute @import rules')
.option('-o, --output [output-file]', 'Use [output-file] as output instead of stdout');
commands.on('--help', function() {
// Now coerce commands into CleanCSS configuration...
if (commands.output)
options.target = commands.output;
-if (commands.args.length > 0)
- options.source = commands.args[0];
if (commands.removeEmpty)
cleanOptions.removeEmpty = true;
if (commands.keepLineBreaks)
cleanOptions.keepSpecialComments = 1;
if (commands.s0)
cleanOptions.keepSpecialComments = 0;
+if (commands.root)
+ cleanOptions.root = commands.root;
+if (commands.args.length > 0) {
+ var source = commands.args[0];
+ options.source = source;
+ cleanOptions.relativeTo = path.dirname(path.resolve(source));
+}
// ... and do the magic!
if (options.source) {
* Copyright (C) 2011-2013 GoalSmashers.com
*/
+var fs = require('fs');
+var path = require('path');
+var existsSync = fs.existsSync || path.existsSync;
+
var CleanCSS = {
colors: {
toHex: {
};
}
+ // inline all imports
+ replace(function inlineImports() {
+ data = CleanCSS._inlineImports(data, {
+ root: options.root || process.cwd(),
+ relativeTo: options.relativeTo
+ });
+ });
+
// strip comments one by one
replace(function stripComments() {
data = CleanCSS._stripComments(context, data);
return data.trim();
},
+ // Inlines all imports taking care of repetitions, unknown files, and cilcular dependencies
+ _inlineImports: function(data, options) {
+ var tempData = [];
+ var nextStart = 0;
+ var nextEnd = 0;
+ var cursor = 0;
+
+ options.relativeTo = options.relativeTo || options.root;
+ options.visited = options.visited || [];
+
+ var inlinedFile = function() {
+ var importedFile = data
+ .substring(data.indexOf('(', nextStart) + 1, nextEnd)
+ .replace(/['"]/g, '');
+
+ var relativeTo = importedFile[0] == '/' ?
+ options.root :
+ options.relativeTo;
+
+ var fullPath = path.resolve(path.join(relativeTo, importedFile));
+
+ if (existsSync(fullPath) && fs.statSync(fullPath).isFile() && options.visited.indexOf(fullPath) == -1) {
+ options.visited.push(fullPath);
+
+ var importedData = fs.readFileSync(fullPath, 'utf8');
+ return CleanCSS._inlineImports(importedData, {
+ root: options.root,
+ relativeTo: path.dirname(fullPath),
+ visited: options.visited
+ });
+ } else {
+ return '';
+ }
+ };
+
+ for (; nextEnd < data.length; ) {
+ nextStart = data.indexOf('@import url(', cursor);
+ if (nextStart == -1)
+ break;
+
+ nextEnd = data.indexOf(')', nextStart);
+ if (nextEnd == -1)
+ break;
+
+ tempData.push(data.substring(cursor, nextStart));
+ tempData.push(inlinedFile());
+ cursor = nextEnd + 2;
+ }
+
+ return tempData.length > 0 ?
+ tempData.join('') + data.substring(cursor, data.length) :
+ data;
+ },
+
// Strip special comments (/*! ... */) by replacing them by __CSSCOMMENT__ marker
// for further restoring. Plain comments are removed. It's done by scanning datq using
// String#indexOf scanning instead of regexps to speed up the process.
var batchContexts = function() {
var context = {};
- fs.readdirSync(path.join(__dirname, 'data')).forEach(function(filename) {
- if (/min.css$/.exec(filename)) return;
+ var dir = path.join(__dirname, 'data');
+ fs.readdirSync(dir).forEach(function(filename) {
+ if (/min.css$/.exec(filename) || !fs.statSync(path.join(dir, filename)).isFile()) return;
var testName = filename.split('.')[0];
context[testName] = {
return {
plain: fs.readFileSync(plainPath, 'utf-8'),
- minimized: fs.readFileSync(minPath, 'utf-8')
+ minimized: fs.readFileSync(minPath, 'utf-8'),
+ root: path.dirname(plainPath)
};
}
};
context[testName]['minimizing ' + testName + '.css'] = function(data) {
var processed = cleanCSS.process(data.plain, {
removeEmpty: true,
- keepBreaks: true
+ keepBreaks: true,
+ root: data.root
});
var processedTokens = processed.split(lineBreak);
assert.equal(stdout, "");
}
}),
+ 'no relative to path': binaryContext('./test/data/partials-absolute/base.css', {
+ 'should not be able to resolve it fully': function(error, stdout) {
+ assert.equal(stdout, ".sub{padding:0}.base{margin:0}");
+ }
+ }),
+ 'relative to path': binaryContext('-r ./test/data ./test/data/partials-absolute/base.css', {
+ 'should be able to resolve it': function(error, stdout) {
+ assert.equal(stdout, ".base2{border-width:0}.sub{padding:0}.base{margin:0}");
+ }
+ }),
'from source': binaryContext('./test/data/reset.css', {
'should minimize': function(error, stdout) {
var minimized = fs.readFileSync('./test/data/reset-min.css', 'utf-8').replace(lineBreak, '');
--- /dev/null
+.one{color:red}
+.three{color:#0f0}
+.four{color:#00f}
+.two{color:#fff}
+.imports{color:#000}
\ No newline at end of file
--- /dev/null
+@import url('./partials/one.css');
+@import url("./partials/two.css");
+
+.imports { color: #000; }
\ No newline at end of file
--- /dev/null
+@import url(./extra/sub.css);
+
+.base { margin:0px }
\ No newline at end of file
--- /dev/null
+.base2 { border-width:0px }
\ No newline at end of file
--- /dev/null
+@import url(/partials-absolute/base2.css);
+
+.sub { padding:0px }
\ No newline at end of file
--- /dev/null
+@import url('../two.css');
+
+.four { color:#00f; }
\ No newline at end of file
--- /dev/null
+.three { color:#0f0; }
\ No newline at end of file
--- /dev/null
+.one { color:#f00; }
--- /dev/null
+@import url('one.css');
+@import url('extra/three.css');
+@import url('./extra/four.css');
+
+.two { color:#fff; }
-var vows = require('vows'),
- assert = require('assert'),
- cleanCSS = require('../index');
+var vows = require('vows');
+var assert = require('assert');
+var path = require('path');
+var cleanCSS = require('../index');
var lineBreak = process.platform == 'win32' ? '\r\n' : '\n';
var cssContext = function(groups, options) {
'empty #2': 'div>a{}',
'empty #3': 'div:nth-child(2n){}',
'empty #4': 'a{color:#fff}div{}p{line-height:2em}'
- })
-}).export(module);
\ No newline at end of file
+ }),
+ '@import': cssContext({
+ 'empty': [
+ "@import url();",
+ ""
+ ],
+ 'of unknown file': [
+ "@import url('fake.css');",
+ ""
+ ],
+ 'of a directory': [
+ "@import url(test/data/partials);",
+ ""
+ ],
+ 'of a real file': [
+ "@import url(test/data/partials/one.css);",
+ ".one{color:red}"
+ ],
+ 'of a real file twice': [
+ "@import url(test/data/partials/one.css);@import url(test/data/partials/one.css);",
+ ".one{color:red}"
+ ],
+ 'of a real file with current path prefix': [
+ "@import url(./test/data/partials/one.css);",
+ ".one{color:red}"
+ ],
+ 'of a real file with quoted path': [
+ "@import url('test/data/partials/one.css');",
+ ".one{color:red}"
+ ],
+ 'of more files': [
+ "@import url(test/data/partials/one.css);\n\na{}\n\n@import url(test/data/partials/extra/three.css);",
+ ".one{color:red}a{}.three{color:#0f0}"
+ ],
+ 'of multi-level, circular dependency file': [
+ "@import url(test/data/partials/two.css);",
+ ".one{color:red}.three{color:#0f0}.four{color:#00f}.two{color:#fff}"
+ ]
+ }),
+ '@import with absolute paths': cssContext({
+ 'of an unknown file': [
+ "@import url(/fake.css);",
+ ""
+ ],
+ 'of a real file': [
+ "@import url(/partials/one.css);",
+ ".one{color:red}"
+ ],
+ 'of a real file with quoted paths': [
+ "@import url(\"/partials/one.css\");",
+ ".one{color:red}"
+ ],
+ 'of two files with mixed paths': [
+ "@import url(/partials/one.css);a{}@import url(partials/extra/three.css);",
+ ".one{color:red}a{}.three{color:#0f0}"
+ ],
+ 'of a multi-level, circular dependency file': [
+ "@import url(/partials/two.css);",
+ ".one{color:red}.three{color:#0f0}.four{color:#00f}.two{color:#fff}"
+ ],
+ 'of a multi-level, circular dependency file with mixed paths': [
+ "@import url(/partials-absolute/base.css);",
+ ".base2{border-width:0}.sub{padding:0}.base{margin:0}"
+ ]
+ }, { root: path.join(process.cwd(), 'test', 'data') })
+}).export(module);