From 57fd0165fb03038a42d17044765dd0be752b62dc Mon Sep 17 00:00:00 2001 From: GoalSmashers Date: Sun, 29 Sep 2013 12:32:05 +0200 Subject: [PATCH] Adds basic optimizer removing duplicate selectors from a list. --- History.md | 1 + lib/clean.js | 4 +-- lib/selectors/optimizer.js | 51 ++++++++++++++++++++++++++++++++++++++ lib/selectors/tokenizer.js | 16 +----------- test/unit-test.js | 24 +++++++++++++++++- 5 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 lib/selectors/optimizer.js diff --git a/History.md b/History.md index d07d5a96..6c48a010 100644 --- a/History.md +++ b/History.md @@ -8,6 +8,7 @@ * Fixed issue [#159](https://github.com/GoalSmashers/clean-css/issues/159) - escaped quotes inside content. * Fixed issue [#162](https://github.com/GoalSmashers/clean-css/issues/162) - strip quotes from base64 encoded URLs. * Adds CSS tokenizer which will make it possible to optimize content by reordering and/or merging selectors. +* Adds basic optimizer removing duplicate selectors from a list. 1.1.7 / 2013-10-28 ================== diff --git a/lib/clean.js b/lib/clean.js index a3e1cbc3..0ab6eea4 100644 --- a/lib/clean.js +++ b/lib/clean.js @@ -20,7 +20,7 @@ var ExpressionsProcessor = require('./text/expressions'); var FreeTextProcessor = require('./text/free'); var UrlsProcessor = require('./text/urls'); -var SelectorsTokenizer = require('./selectors/tokenizer'); +var SelectorsOptimizer = require('./selectors/optimizer'); var CleanCSS = { process: function(data, options) { @@ -248,7 +248,7 @@ var CleanCSS = { }); replace(function optimizeSelectors() { - data = new SelectorsTokenizer(data).process(); + data = new SelectorsOptimizer(data).process(); }); replace(function restoreUrls() { diff --git a/lib/selectors/optimizer.js b/lib/selectors/optimizer.js new file mode 100644 index 00000000..cacef242 --- /dev/null +++ b/lib/selectors/optimizer.js @@ -0,0 +1,51 @@ +var Tokenizer = require('./tokenizer'); + +module.exports = function Optimizer(data) { + var stripRepeats = function(selectors) { + var plain = []; + selectors = selectors.split(','); + + for (var i = 0, l = selectors.length; i < l; i++) { + var sel = selectors[i]; + + if (plain.indexOf(sel) == -1) + plain.push(sel); + } + + return plain.join(','); + }; + + var optimize = function(tokens) { + tokens = (Array.isArray(tokens) ? tokens : [tokens]); + for (var i = 0, l = tokens.length; i < l; i++) { + var token = tokens[i]; + + if (token.selector) + token.selector = stripRepeats(token.selector); + if (token.block) + optimize(token.body); + } + }; + + var rebuild = function(tokens) { + return (Array.isArray(tokens) ? tokens : [tokens]) + .map(function(token) { + if (typeof token == 'string') + return token; + + if (token.block) + return token.block + '{' + rebuild(token.body) + '}'; + else + return token.selector + '{' + token.body + '}'; + }) + .join(''); + }; + + return { + process: function() { + var tokenized = new Tokenizer(data).process(); + optimize(tokenized); + return rebuild(tokenized); + } + }; +}; diff --git a/lib/selectors/tokenizer.js b/lib/selectors/tokenizer.js index 236d4364..8fab9401 100644 --- a/lib/selectors/tokenizer.js +++ b/lib/selectors/tokenizer.js @@ -106,23 +106,9 @@ module.exports = function Tokenizer(data) { return tokenized; }; - var rebuild = function(tokens) { - return (Array.isArray(tokens) ? tokens : [tokens]) - .map(function(token) { - if (typeof token == 'string') - return token; - - if (token.block) - return token.block + '{' + rebuild(token.body) + '}'; - else - return token.selector + '{' + token.body + '}'; - }) - .join(''); - }; - return { process: function() { - return rebuild(tokenize()); + return tokenize(); } }; }; diff --git a/test/unit-test.js b/test/unit-test.js index 3c7a88bc..e95a92c4 100644 --- a/test/unit-test.js +++ b/test/unit-test.js @@ -1035,5 +1035,27 @@ title']{display:block}", "@import url(/fake.css);", "@import url(/fake.css);" ] - }, { processImport: false }) + }, { processImport: false }), + 'duplicate selectors in a list': cssContext({ + 'of a duplicate selector': [ + 'a,a{color:red}', + 'a{color:red}' + ], + 'of an unordered multiply repeated selector': [ + 'a,b,p,a{color:red}', + 'a,b,p{color:red}' + ], + 'of an unordered multiply repeated selector within a block': [ + '@media screen{a,b,p,a{color:red}}', + '@media screen{a,b,p{color:red}}' + ], + 'of an unordered multiply repeated complex selector within a block #1': [ + '@media screen{a,.link[data-path],p,.link[data-path]{color:red}}', + '@media screen{a,.link[data-path],p{color:red}}' + ], + 'of an unordered multiply repeated complex selector within a block #2': [ + '@media screen{a,#foo[data-path^="bar bar"],p,#foo[data-path^="bar bar"]{color:red}}', + '@media screen{a,#foo[data-path^="bar bar"],p{color:red}}' + ] + }) }).export(module); -- 2.34.1