var removeDuplicates = require('./remove-duplicates');
var mergeAdjacent = require('./merge-adjacent');
+var reduceNonAdjacent = require('./reduce-non-adjacent');
function AdvancedOptimizer(options, context) {
this.options = options;
return this.options.compatibility.selectors.special.test(selector);
};
-AdvancedOptimizer.prototype.reduceNonAdjacent = function (tokens) {
- var candidates = {};
- var repeated = [];
-
- for (var i = tokens.length - 1; i >= 0; i--) {
- var token = tokens[i];
-
- if (token[0] != 'selector')
- continue;
- if (token[2].length === 0)
- continue;
-
- var selectorAsString = stringifySelectors(token[1]);
- var isComplexAndNotSpecial = token[1].length > 1 && !this.isSpecial(selectorAsString);
- var selectors = isComplexAndNotSpecial ?
- [selectorAsString].concat(token[1]) :
- [selectorAsString];
-
- for (var j = 0, m = selectors.length; j < m; j++) {
- var selector = selectors[j];
-
- if (!candidates[selector])
- candidates[selector] = [];
- else
- repeated.push(selector);
-
- candidates[selector].push({
- where: i,
- list: token[1],
- isPartial: isComplexAndNotSpecial && j > 0,
- isComplex: isComplexAndNotSpecial && j === 0
- });
- }
- }
-
- this.reduceSimpleNonAdjacentCases(tokens, repeated, candidates);
- this.reduceComplexNonAdjacentCases(tokens, candidates);
-};
-
-AdvancedOptimizer.prototype.reduceSimpleNonAdjacentCases = function (tokens, repeated, candidates) {
- function filterOut(idx, bodies) {
- return data[idx].isPartial && bodies.length === 0;
- }
-
- function reduceBody(token, newBody, processedCount, tokenIdx) {
- if (!data[processedCount - tokenIdx - 1].isPartial)
- token[2] = newBody;
- }
-
- for (var i = 0, l = repeated.length; i < l; i++) {
- var selector = repeated[i];
- var data = candidates[selector];
-
- this.reduceSelector(tokens, selector, data, {
- filterOut: filterOut,
- callback: reduceBody
- });
- }
-};
-
-AdvancedOptimizer.prototype.reduceComplexNonAdjacentCases = function (tokens, candidates) {
- var localContext = {};
-
- function filterOut(idx) {
- return localContext.data[idx].where < localContext.intoPosition;
- }
-
- function collectReducedBodies(token, newBody, processedCount, tokenIdx) {
- if (tokenIdx === 0)
- localContext.reducedBodies.push(newBody);
- }
-
- allSelectors:
- for (var complexSelector in candidates) {
- var into = candidates[complexSelector];
- if (!into[0].isComplex)
- continue;
-
- var intoPosition = into[into.length - 1].where;
- var intoToken = tokens[intoPosition];
- var reducedBodies = [];
-
- var selectors = this.isSpecial(complexSelector) ?
- [complexSelector] :
- into[0].list;
-
- localContext.intoPosition = intoPosition;
- localContext.reducedBodies = reducedBodies;
-
- for (var j = 0, m = selectors.length; j < m; j++) {
- var selector = selectors[j];
- var data = candidates[selector];
-
- if (data.length < 2)
- continue allSelectors;
-
- localContext.data = data;
-
- this.reduceSelector(tokens, selector, data, {
- filterOut: filterOut,
- callback: collectReducedBodies
- });
-
- if (stringifyBody(reducedBodies[reducedBodies.length - 1]) != stringifyBody(reducedBodies[0]))
- continue allSelectors;
- }
-
- intoToken[2] = reducedBodies[0];
- }
-};
-
-AdvancedOptimizer.prototype.reduceSelector = function (tokens, selector, data, options) {
- var bodies = [];
- var bodiesAsList = [];
- var joinsAt = [];
- var processedTokens = [];
-
- for (var j = data.length - 1, m = 0; j >= 0; j--) {
- if (options.filterOut(j, bodies))
- continue;
-
- var where = data[j].where;
- var token = tokens[where];
-
- bodies = bodies.concat(token[2]);
- bodiesAsList.push(token[2]);
- processedTokens.push(where);
- }
-
- for (j = 0, m = bodiesAsList.length; j < m; j++) {
- if (bodiesAsList[j].length > 0)
- joinsAt.push((joinsAt[j - 1] || 0) + bodiesAsList[j].length);
- }
-
- optimizeProperties(selector, bodies, joinsAt, false, this.options, this.validator);
-
- var processedCount = processedTokens.length;
- var propertyIdx = bodies.length - 1;
- var tokenIdx = processedCount - 1;
-
- while (tokenIdx >= 0) {
- if ((tokenIdx === 0 || (bodies[propertyIdx] && bodiesAsList[tokenIdx].indexOf(bodies[propertyIdx]) > -1)) && propertyIdx > -1) {
- propertyIdx--;
- continue;
- }
-
- var newBody = bodies.splice(propertyIdx + 1);
- options.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx);
-
- tokenIdx--;
- }
-};
-
AdvancedOptimizer.prototype.mergeNonAdjacentBySelector = function (tokens) {
var allSelectors = {};
var repeatedSelectors = [];
removeDuplicates(tokens);
mergeAdjacent(tokens, self.options, self.validator);
- self.reduceNonAdjacent(tokens);
+ reduceNonAdjacent(tokens, self.options, self.validator);
self.mergeNonAdjacentBySelector(tokens);
self.mergeNonAdjacentByBody(tokens);
--- /dev/null
+var optimizeProperties = require('../properties/optimizer');
+var stringifyBody = require('../stringifier/one-time').body;
+var stringifySelectors = require('../stringifier/one-time').selectors;
+var isSpecial = require('./is-special');
+
+function reduceNonAdjacent(tokens, options, validator) {
+ var candidates = {};
+ var repeated = [];
+
+ for (var i = tokens.length - 1; i >= 0; i--) {
+ var token = tokens[i];
+
+ if (token[0] != 'selector')
+ continue;
+ if (token[2].length === 0)
+ continue;
+
+ var selectorAsString = stringifySelectors(token[1]);
+ var isComplexAndNotSpecial = token[1].length > 1 && !isSpecial(options, selectorAsString);
+ var selectors = isComplexAndNotSpecial ?
+ [selectorAsString].concat(token[1]) :
+ [selectorAsString];
+
+ for (var j = 0, m = selectors.length; j < m; j++) {
+ var selector = selectors[j];
+
+ if (!candidates[selector])
+ candidates[selector] = [];
+ else
+ repeated.push(selector);
+
+ candidates[selector].push({
+ where: i,
+ list: token[1],
+ isPartial: isComplexAndNotSpecial && j > 0,
+ isComplex: isComplexAndNotSpecial && j === 0
+ });
+ }
+ }
+
+ reduceSimpleNonAdjacentCases(tokens, repeated, candidates, options, validator);
+ reduceComplexNonAdjacentCases(tokens, candidates, options, validator);
+}
+
+function reduceSimpleNonAdjacentCases(tokens, repeated, candidates, options, validator) {
+ function filterOut(idx, bodies) {
+ return data[idx].isPartial && bodies.length === 0;
+ }
+
+ function reduceBody(token, newBody, processedCount, tokenIdx) {
+ if (!data[processedCount - tokenIdx - 1].isPartial)
+ token[2] = newBody;
+ }
+
+ for (var i = 0, l = repeated.length; i < l; i++) {
+ var selector = repeated[i];
+ var data = candidates[selector];
+
+ reduceSelector(tokens, selector, data, {
+ filterOut: filterOut,
+ callback: reduceBody
+ }, options, validator);
+ }
+}
+
+function reduceComplexNonAdjacentCases(tokens, candidates, options, validator) {
+ var localContext = {};
+
+ function filterOut(idx) {
+ return localContext.data[idx].where < localContext.intoPosition;
+ }
+
+ function collectReducedBodies(token, newBody, processedCount, tokenIdx) {
+ if (tokenIdx === 0)
+ localContext.reducedBodies.push(newBody);
+ }
+
+ allSelectors:
+ for (var complexSelector in candidates) {
+ var into = candidates[complexSelector];
+ if (!into[0].isComplex)
+ continue;
+
+ var intoPosition = into[into.length - 1].where;
+ var intoToken = tokens[intoPosition];
+ var reducedBodies = [];
+
+ var selectors = isSpecial(options, complexSelector) ?
+ [complexSelector] :
+ into[0].list;
+
+ localContext.intoPosition = intoPosition;
+ localContext.reducedBodies = reducedBodies;
+
+ for (var j = 0, m = selectors.length; j < m; j++) {
+ var selector = selectors[j];
+ var data = candidates[selector];
+
+ if (data.length < 2)
+ continue allSelectors;
+
+ localContext.data = data;
+
+ reduceSelector(tokens, selector, data, {
+ filterOut: filterOut,
+ callback: collectReducedBodies
+ }, options, validator);
+
+ if (stringifyBody(reducedBodies[reducedBodies.length - 1]) != stringifyBody(reducedBodies[0]))
+ continue allSelectors;
+ }
+
+ intoToken[2] = reducedBodies[0];
+ }
+}
+
+function reduceSelector(tokens, selector, data, context, options, validator) {
+ var bodies = [];
+ var bodiesAsList = [];
+ var joinsAt = [];
+ var processedTokens = [];
+
+ for (var j = data.length - 1, m = 0; j >= 0; j--) {
+ if (context.filterOut(j, bodies))
+ continue;
+
+ var where = data[j].where;
+ var token = tokens[where];
+
+ bodies = bodies.concat(token[2]);
+ bodiesAsList.push(token[2]);
+ processedTokens.push(where);
+ }
+
+ for (j = 0, m = bodiesAsList.length; j < m; j++) {
+ if (bodiesAsList[j].length > 0)
+ joinsAt.push((joinsAt[j - 1] || 0) + bodiesAsList[j].length);
+ }
+
+ optimizeProperties(selector, bodies, joinsAt, false, options, validator);
+
+ var processedCount = processedTokens.length;
+ var propertyIdx = bodies.length - 1;
+ var tokenIdx = processedCount - 1;
+
+ while (tokenIdx >= 0) {
+ if ((tokenIdx === 0 || (bodies[propertyIdx] && bodiesAsList[tokenIdx].indexOf(bodies[propertyIdx]) > -1)) && propertyIdx > -1) {
+ propertyIdx--;
+ continue;
+ }
+
+ var newBody = bodies.splice(propertyIdx + 1);
+ context.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx);
+
+ tokenIdx--;
+ }
+}
+
+module.exports = reduceNonAdjacent;
]
}, { aggressiveMerging: false })
)
- .addBatch(
- optimizerContext('same non-adjacent selectors', {
- 'with one redefined property': [
- 'a{color:red;display:block}.one{color:red}a{color:#fff;margin:2px}',
- '.one{color:red}a{display:block;color:#fff;margin:2px}'
- ],
- 'with intentionally redefined properties on joins': [
- 'a{display:inline-block;display:-moz-inline-box;color:red}.one{margin:12px}a{color:#fff;margin:2px}',
- '.one{margin:12px}a{display:inline-block;display:-moz-inline-box;color:#fff;margin:2px}'
- ],
- 'with intentionally redefined properties on multiple joins': [
- 'a{color:red}.one{font-size:12px}a{color:#fff;margin:2px}.two{margin:10px}a{margin:0}',
- '.one{font-size:12px}.two{margin:10px}a{color:#fff;margin:0}'
- ],
- 'with all redefined properties': [
- 'a{color:red;display:block}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}',
- '.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}'
- ],
- 'many with all redefined properties': [
- 'a{padding:10px}.zero{color:transparent}a{color:red;display:block}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}',
- '.zero{color:transparent}.one{font-size:12px}a{padding:10px;color:#fff;display:inline-block;margin:2px}'
- ],
- 'when overriden by an empty selector': [
- 'a{padding:10px}.one{color:red}a{}',
- 'a{padding:10px}.one{color:red}'
- ],
- 'when overriden by a complex selector': [
- 'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
- '.one,a,p{color:red}a{margin:0}a,p{padding:0}'
- ],
- 'when overriden by complex selectors': [
- 'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}.one,a{color:#fff}',
- 'a{margin:0}a,p{color:red;padding:0}.one,a{color:#fff}'
- ],
- 'when complex selector overriden by simple selectors': [
- 'a,p{margin:0;color:red}a{color:#fff}',
- 'a,p{margin:0;color:red}a{color:#fff}'
- ],
- 'when complex selector overriden by complex and simple selectors': [
- 'a,p{margin:0;color:red}a{color:#fff}a,p{color:#00f}p{color:#0f0}',
- 'a,p{margin:0;color:#00f}p{color:#0f0}'
- ],
- 'when complex selector overriden by complex selectors': [
- '.one>.two,.three{color:red;line-height:1rem}#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}',
- '#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}'
- ],
- 'when undefined is used as a value': [
- '.one{text-shadow:undefined}p{font-size:14px}.one{font-size:12px}',
- 'p{font-size:14px}.one{text-shadow:undefined;font-size:12px}'
- ],
- 'when undefined is used as a value with reduction': [
- '.one{text-shadow:undefined}p{color:red}.one{font-size:12px;text-shadow:none}',
- 'p{color:red}.one{font-size:12px;text-shadow:none}'
- ],
- 'when overriden with a browser specific selector': [
- 'a{color:red}p{display:block}::-moz-selection,a{color:#fff}',
- 'a{color:red}p{display:block}::-moz-selection,a{color:#fff}'
- ],
- 'when same browser specific selector more than once': [
- 'a,::-moz-selection{color:red}p{display:block}a,::-moz-selection{color:#fff}',
- 'p{display:block}::-moz-selection,a{color:#fff}'
- ],
- 'with full property comparison': [
- '.one{height:7rem}.two{color:#fff}.one{line-height:7rem;color:red}',
- '.two{color:#fff}.one{height:7rem;line-height:7rem;color:red}'
- ],
- 'with two intermediate, non-overriding selectors': [
- '.one{color:red;margin:0}.two{color:#fff}.one{font-size:12px}',
- '.one{color:red;margin:0;font-size:12px}.two{color:#fff}'
- ],
- 'with two intermediate, overriding more specific selectors': [
- '.one{color:red;margin:0}.two{font:12px serif}.one{font-size:12px}',
- '.two{font:12px serif}.one{color:red;margin:0;font-size:12px}'
- ],
- 'with granular selectors from the same shorthand': [
- '.one{color:red;margin:0}.two{font-weight:700}.one{font-size:12px}',
- '.one{color:red;margin:0;font-size:12px}.two{font-weight:700}'
- ],
- 'with three intermediate, non-overriding selectors': [
- '.one{color:red;margin:0}.two{color:#fff}.one{font-size:12px}.three{color:#000}.one{padding:0}',
- '.one{color:red;margin:0;font-size:12px;padding:0}.two{color:#fff}.three{color:#000}'
- ],
- 'successive selectors': [
- 'footer,header{top:1.25em;bottom:1.25em}header{top:2.5em}footer{bottom:2.5em}',
- 'footer,header{top:1.25em;bottom:1.25em}header{top:2.5em}footer{bottom:2.5em}'
- ],
- 'over a @media block': [
- '.one{color:red;margin:0}@media{.two{font-weight:700}}.one{font-size:12px}',
- '.one{color:red;margin:0;font-size:12px}@media{.two{font-weight:700}}'
- ]
- })
- )
.addBatch(
optimizerContext('rerun optimizers', {
'selectors reducible once': [
'repeated' : [
'a{color:red;color:red}',
'a{color:red}'
- ],
- 'non-adjacent': [
- 'a{color:red;display:block}.one{margin:12px}a{color:#fff;margin:2px}',
- '.one{margin:12px}a{display:block;color:#fff;margin:2px}'
- ],
- 'non-adjacent with multi selectors': [
- 'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
- '.one,a,p{color:red}a{margin:0}a,p{padding:0}'
]
}, { advanced: true, aggressiveMerging: true })
)
'repeated' : [
'a{color:red;color:red}',
'a{color:red}'
- ],
- 'non-adjacent with multi selectors': [
- 'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
- '.one,a,p{color:red}a{padding:10px;margin:0}a,p{padding:0}'
]
}, { advanced: true, aggressiveMerging: false })
)
'repeated' : [
'a{color:red;color:red}',
'a{color:red;color:red}'
- ],
- 'non-adjacent': [
- 'a{color:red;display:block}.one{font-size:12px}a{color:#fff;margin:2px}',
- 'a{color:red;display:block}.one{font-size:12px}a{color:#fff;margin:2px}'
]
}, { advanced: false })
)
--- /dev/null
+var vows = require('vows');
+var optimizerContext = require('../test-helper').optimizerContext;
+
+vows.describe('remove duplicates')
+ .addBatch(
+ optimizerContext('advanced on', {
+ 'single selectors': [
+ 'a{color:red;display:block}.one{margin:12px}a{color:#fff;margin:2px}',
+ '.one{margin:12px}a{display:block;color:#fff;margin:2px}'
+ ],
+ 'multiple selectors': [
+ 'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
+ '.one,a,p{color:red}a{margin:0}a,p{padding:0}'
+ ],
+ 'with one redefined property': [
+ 'a{color:red;display:block}.one{color:red}a{color:#fff;margin:2px}',
+ '.one{color:red}a{display:block;color:#fff;margin:2px}'
+ ],
+ 'with intentionally redefined properties on joins': [
+ 'a{display:inline-block;display:-moz-inline-box;color:red}.one{margin:12px}a{color:#fff;margin:2px}',
+ '.one{margin:12px}a{display:inline-block;display:-moz-inline-box;color:#fff;margin:2px}'
+ ],
+ 'with intentionally redefined properties on multiple joins': [
+ 'a{color:red}.one{font-size:12px}a{color:#fff;margin:2px}.two{margin:10px}a{margin:0}',
+ '.one{font-size:12px}.two{margin:10px}a{color:#fff;margin:0}'
+ ],
+ 'with all redefined properties': [
+ 'a{color:red;display:block}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}',
+ '.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}'
+ ],
+ 'many with all redefined properties': [
+ 'a{padding:10px}.zero{color:transparent}a{color:red;display:block}.one{font-size:12px}a{color:#fff;display:inline-block;margin:2px}',
+ '.zero{color:transparent}.one{font-size:12px}a{padding:10px;color:#fff;display:inline-block;margin:2px}'
+ ],
+ 'when overriden by an empty selector': [
+ 'a{padding:10px}.one{color:red}a{}',
+ 'a{padding:10px}.one{color:red}'
+ ],
+ 'when overriden by a complex selector': [
+ 'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
+ '.one,a,p{color:red}a{margin:0}a,p{padding:0}'
+ ],
+ 'when overriden by complex selectors': [
+ 'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}.one,a{color:#fff}',
+ 'a{margin:0}a,p{color:red;padding:0}.one,a{color:#fff}'
+ ],
+ 'when complex selector overriden by simple selectors': [
+ 'a,p{margin:0;color:red}a{color:#fff}',
+ 'a,p{margin:0;color:red}a{color:#fff}'
+ ],
+ 'when complex selector overriden by complex and simple selectors': [
+ 'a,p{margin:0;color:red}a{color:#fff}a,p{color:#00f}p{color:#0f0}',
+ 'a,p{margin:0;color:#00f}p{color:#0f0}'
+ ],
+ 'when complex selector overriden by complex selectors': [
+ '.one>.two,.three{color:red;line-height:1rem}#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}',
+ '#zero,.one>.two,.three,.www{color:#fff;margin:0}a{color:red}.one>.two,.three{line-height:2rem;font-size:1.5rem}'
+ ],
+ 'when undefined is used as a value': [
+ '.one{text-shadow:undefined}p{font-size:14px}.one{font-size:12px}',
+ 'p{font-size:14px}.one{text-shadow:undefined;font-size:12px}'
+ ],
+ 'when undefined is used as a value with reduction': [
+ '.one{text-shadow:undefined}p{color:red}.one{font-size:12px;text-shadow:none}',
+ 'p{color:red}.one{font-size:12px;text-shadow:none}'
+ ],
+ 'when overriden with a browser specific selector': [
+ 'a{color:red}p{display:block}::-moz-selection,a{color:#fff}',
+ 'a{color:red}p{display:block}::-moz-selection,a{color:#fff}'
+ ],
+ 'when same browser specific selector more than once': [
+ 'a,::-moz-selection{color:red}p{display:block}a,::-moz-selection{color:#fff}',
+ 'p{display:block}::-moz-selection,a{color:#fff}'
+ ],
+ 'with full property comparison': [
+ '.one{height:7rem}.two{color:#fff}.one{line-height:7rem;color:red}',
+ '.two{color:#fff}.one{height:7rem;line-height:7rem;color:red}'
+ ],
+ 'with two intermediate, non-overriding selectors': [
+ '.one{color:red;margin:0}.two{color:#fff}.one{font-size:12px}',
+ '.one{color:red;margin:0;font-size:12px}.two{color:#fff}'
+ ],
+ 'with two intermediate, overriding more specific selectors': [
+ '.one{color:red;margin:0}.two{font:12px serif}.one{font-size:12px}',
+ '.two{font:12px serif}.one{color:red;margin:0;font-size:12px}'
+ ],
+ 'with granular selectors from the same shorthand': [
+ '.one{color:red;margin:0}.two{font-weight:700}.one{font-size:12px}',
+ '.one{color:red;margin:0;font-size:12px}.two{font-weight:700}'
+ ],
+ 'with three intermediate, non-overriding selectors': [
+ '.one{color:red;margin:0}.two{color:#fff}.one{font-size:12px}.three{color:#000}.one{padding:0}',
+ '.one{color:red;margin:0;font-size:12px;padding:0}.two{color:#fff}.three{color:#000}'
+ ],
+ 'successive selectors': [
+ 'footer,header{top:1.25em;bottom:1.25em}header{top:2.5em}footer{bottom:2.5em}',
+ 'footer,header{top:1.25em;bottom:1.25em}header{top:2.5em}footer{bottom:2.5em}'
+ ],
+ 'over a @media block': [
+ '.one{color:red;margin:0}@media{.two{font-weight:700}}.one{font-size:12px}',
+ '.one{color:red;margin:0;font-size:12px}@media{.two{font-weight:700}}'
+ ]
+ })
+ )
+ .addBatch(
+ optimizerContext('advanced on and aggressive merging off', {
+ 'non-adjacent with multi selectors': [
+ 'a{padding:10px;margin:0;color:red}.one{color:red}a,p{color:red;padding:0}',
+ '.one,a,p{color:red}a{padding:10px;margin:0}a,p{padding:0}'
+ ]
+ }, { aggressiveMerging: false })
+ )
+ .addBatch(
+ optimizerContext('advanced off', {
+ 'non-adjacent': [
+ 'a{color:red;display:block}.one{font-size:12px}a{color:#fff;margin:2px}',
+ 'a{color:red;display:block}.one{font-size:12px}a{color:#fff;margin:2px}'
+ ]
+ }, { advanced: false })
+ )
+ .export(module);