From: Jakub Pawlowicz Date: Sat, 22 Feb 2014 22:23:20 +0000 (+0000) Subject: Adds a better non-adjacent optimizer. X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=ebe6d60240aa450463377d7c2970c74b55686519;p=clean-css.git Adds a better non-adjacent optimizer. * Makes code easier to understand by splitting simple and complex scenarios. * Adds compatibility with upcoming new property optimizer. --- diff --git a/History.md b/History.md index 6b1ad360..93078bad 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +[2.2.0 / 2014-xx-xx (UNRELEASED)](https://github.com/GoalSmashers/clean-css/compare/v2.1.1...HEAD) +================== + +* Adds a better non-adjacent optimizer compatible with the upcoming new property optimizer. + [2.1.1 / 2014-02-18](https://github.com/GoalSmashers/clean-css/compare/v2.1.0...v2.1.1) ================== diff --git a/lib/selectors/optimizer.js b/lib/selectors/optimizer.js index fe394cf5..c55c155a 100644 --- a/lib/selectors/optimizer.js +++ b/lib/selectors/optimizer.js @@ -123,108 +123,149 @@ module.exports = function Optimizer(data, context, options) { }; var reduceNonAdjacent = function(tokens) { - var matched = {}; - var matchedMoreThanOnce = []; - var partiallyReduced = []; - var reduced = false; - var token, selector, selectors; + var candidates = {}; + var moreThanOnce = []; - for (var i = 0, l = tokens.length; i < l; i++) { - token = tokens[i]; - selector = token.selector; + for (var i = tokens.length - 1; i >= 0; i--) { + var token = tokens[i]; if (typeof token == 'string' || token.block) continue; - selectors = selector.indexOf(',') > 0 ? - selector.split(',').concat(selector) : - [selector]; + var complexSelector = token.selector; + var selectors = complexSelector.split(','); // simplification, as :not() can have commas too + + if (selectors.length > 1) + selectors.unshift(complexSelector); for (var j = 0, m = selectors.length; j < m; j++) { - var sel = selectors[j]; - var alreadyMatched = matched[sel]; - if (alreadyMatched) { - if (alreadyMatched.length == 1) - matchedMoreThanOnce.push(sel); - alreadyMatched.push(i); - } else { - matched[sel] = [i]; - } + var selector = selectors[j]; + + if (!candidates[selector]) + candidates[selector] = []; + else + moreThanOnce.push(selector); + + candidates[selector].push({ + where: i, + partial: selector != complexSelector + }); } } - matchedMoreThanOnce.forEach(function(selector) { - var matchPositions = matched[selector]; + _reduceSimpleNonAdjacentCases(tokens, moreThanOnce, candidates); + _reduceComplexNonAdjacentCases(tokens, candidates); + }; + + var _reduceSimpleNonAdjacentCases = function(tokens, matches, positions) { + for (var i = 0, l = matches.length; i < l; i++) { + var selector = matches[i]; + var data = positions[selector]; var bodies = []; var joinsAt = []; - var j; + var processedTokens = []; + + for (var j = data.length - 1, m = 0; j >= 0; j--) { + if (data[j].partial && bodies.length === 0) + continue; - for (j = 0, m = matchPositions.length; j < m; j++) { - var body = tokens[matchPositions[j]].body; + var where = data[j].where; + var token = tokens[where]; + var body = token.body; bodies.push(body); - joinsAt.push((joinsAt[j - 1] || 0) + body.split(';').length); + processedTokens.push(where); + } + + for (j = 0, m = bodies.length; j < m; j++) { + if (bodies[j].length > 0) + joinsAt.push((joinsAt[j - 1] || 0) + bodies[j].split(';').length); } var optimizedBody = propertyOptimizer.process(bodies.join(';'), joinsAt); - var optimizedTokens = optimizedBody.split(';'); + var optimizedProperties = optimizedBody.split(';'); - j = optimizedTokens.length - 1; - var currentMatch = matchPositions.length - 1; + var processedCount = processedTokens.length; + var propertyIdx = optimizedProperties.length - 1; + var tokenIdx = processedCount - 1; - while (currentMatch >= 0) { - if (bodies[currentMatch].indexOf(optimizedTokens[j]) > -1 && j > -1) { - j -= 1; + while (tokenIdx >= 0) { + if ((tokenIdx === 0 || bodies[tokenIdx].indexOf(optimizedProperties[propertyIdx]) > -1) && propertyIdx > -1) { + propertyIdx--; continue; } - var tokenIndex = matchPositions[currentMatch]; - var token = tokens[tokenIndex]; - var newBody = optimizedTokens.splice(j + 1); - var reducedBody = []; - for (var k = 0, n = newBody.length; k < n; k++) { - if (newBody[k].length > 0) - reducedBody.push(newBody[k]); - } + var newBody = optimizedProperties.splice(propertyIdx + 1); + if (!data[processedCount - tokenIdx - 1].partial) + tokens[processedTokens[tokenIdx]].body = newBody.join(';'); + + tokenIdx--; + } + } + }; - if (token.selector == selector) { - var joinedBody = reducedBody.join(';'); - reduced = reduced || (token.body != joinedBody); - token.body = joinedBody; - } else { - token._partials = token._partials || []; - token._partials.push(reducedBody.join(';')); + var _reduceComplexNonAdjacentCases = function(tokens, positions) { + for (var i = 0, matches = Object.keys(positions), l = matches.length; i < l; i++) { + var complexSelector = matches[i]; + var allSame = true; - if (partiallyReduced.indexOf(tokenIndex) == -1) - partiallyReduced.push(tokenIndex); - } + if (complexSelector.indexOf(',') == -1) // another assumption which is wrong in case of :not() selector + continue; - currentMatch -= 1; - } - }); + var intoPosition = positions[complexSelector].pop().where; + var intoToken = tokens[intoPosition]; + + var selectors = complexSelector.split(','); + var reducedBodies = []; + + for (var j = 0, m = selectors.length; j < m; j++) { + var selector = selectors[j]; + var data = positions[selector]; + var bodies = []; + var joinsAt = []; + var processedTokens = []; + + for (var k = data.length - 1, n = 0; k >= 0; k--) { + var where = data[k].where; + if (where < intoPosition) + continue; + + var token = tokens[where]; + var body = token.body; + bodies.push(body); + processedTokens.push(where); + } - // process those tokens which were partially reduced - // i.e. at least one of token's selectors saw reduction - // if all selectors were reduced to same value we can override it - for (i = 0, l = partiallyReduced.length; i < l; i++) { - token = tokens[partiallyReduced[i]]; - - if (token.body != token._partials[0] && token._partials.length == token.selector.split(',').length) { - var newBody = token._partials[0]; - for (var k = 1, n = token._partials.length; k < n; k++) { - if (token._partials[k] != newBody) - break; + for (k = 0, n = bodies.length; k < n; k++) { + if (bodies[k].length > 0) + joinsAt.push((joinsAt[k - 1] || 0) + bodies[k].split(';').length); } - if (k == n) { - token.body = newBody; - reduced = reduced || true; + var optimizedBody = propertyOptimizer.process(bodies.join(';'), joinsAt); + var optimizedProperties = optimizedBody.split(';'); + + var processedCount = processedTokens.length; + var propertyIdx = optimizedProperties.length - 1; + var tokenIdx = processedCount - 1; + + while (tokenIdx >= 0) { + if ((tokenIdx === 0 || bodies[tokenIdx].indexOf(optimizedProperties[propertyIdx]) > -1) && propertyIdx > -1) { + propertyIdx--; + continue; + } + + var newBody = optimizedProperties.splice(propertyIdx + 1); + if (tokenIdx === 0) + reducedBodies.push(newBody.join(';')); + + tokenIdx--; } + + allSame = allSame && reducedBodies[reducedBodies.length - 1] == reducedBodies[0]; } - delete token._partials; + if (allSame) + intoToken.body = reducedBodies[0]; } - - minificationsMade.unshift(reduced); }; var optimize = function(tokens) {