Socket
Socket
Sign inDemoInstall

csso

Package Overview
Dependencies
9
Maintainers
3
Versions
82
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.7.1 to 1.8.0

lib/compressor/restructure/prepare/createDeclarationIndexer.js

27

HISTORY.md

@@ -0,1 +1,16 @@

## 1.8.0 (March 24, 2016)
- Usage data support:
- Filter rulesets by tag names, class names and ids white lists.
- More aggressive ruleset moving using class name scopes information.
- New CLI option `--usage` to pass usage data file.
- Improve initial ruleset merge
- Change order of ruleset processing, now it's left to right. Previously unmerged rulesets may prevent lookup and other rulesets merge.
- Difference in pseudo signature just prevents ruleset merging, but don't stop lookup.
- Simplify block comparison (performance).
- New method `csso.minifyBlock()` for css block compression (e.g. `style` attribute content).
- Ruleset merge improvement: at-rules with block (like `@media` or `@supports`) now can be skipped during ruleset merge lookup if doesn't contain something prevents it.
- FIX: Add negation (`:not()`) to pseudo signature to avoid unsafe merge (old browsers doesn't support it).
- FIX: Check nested parts of value when compute compatibility. It fixes unsafe property merging.
## 1.7.1 (March 16, 2016)

@@ -93,3 +108,3 @@

- `outputAst` – new option to specify output AST format (`gonzales` by default for backward compatibility)
- remove quotes surrounding attribute values in attribute selectors when possible (issue #73)
- remove quotes surrounding attribute values in attribute selectors when possible (#73)
- replace `from`→`0%` and `100%`→`to` at `@keyframes` (#205)

@@ -209,8 +224,8 @@ - prevent partial merge of rulesets at `@keyframes` (#80, #197)

- Case insensitive check for `!important` (issue #187)
- Fix problems with using `csso` as cli command on Windows (issue #83, #136, #142 and others)
- Case insensitive check for `!important` (#187)
- Fix problems with using `csso` as cli command on Windows (#83, #136, #142 and others)
- Remove byte order marker (the UTF-8 BOM) from input
- Don't strip space between funktion-funktion and funktion-vhash (issue #134)
- Don't merge TRBL values having \9 (hack for IE8 in bootstrap) (issues #159, #214, #230, #231 and others)
- Don't strip units off dimensions of non-length (issues #226, #229 and others)
- Don't strip space between funktion-funktion and funktion-vhash (#134)
- Don't merge TRBL values having \9 (hack for IE8 in bootstrap) (#159, #214, #230, #231 and others)
- Don't strip units off dimensions of non-length (#226, #229 and others)

@@ -217,0 +232,0 @@ ## 1.3.7 (February 11, 2013)

@@ -180,4 +180,5 @@ var fs = require('fs');

.option('-o, --output <filename>', 'Output file (result outputs to stdout if not set)')
.option('-m, --map <destination>', 'Generate source map. Possible values: none (default), inline, file or <filename>', 'none')
.option('--input-map <source>', 'Input source map. Possible values: none, auto (default) or <filename>', 'auto')
.option('-m, --map <destination>', 'Generate source map: none (default), inline, file or <filename>', 'none')
.option('-u, --usage <filenane>', 'Usage data file')
.option('--input-map <source>', 'Input source map: none, auto (default) or <filename>', 'auto')
.option('--restructure-off', 'Turns structure minimization off')

@@ -190,2 +191,4 @@ .option('--stat', 'Output statistics in stderr')

var outputFile = options.output || args[1];
var usageFile = options.usage;
var usageData = false;
var map = options.map;

@@ -215,2 +218,18 @@ var inputMap = options.inputMap;

if (usageFile) {
if (!fs.existsSync(usageFile)) {
console.error('Usage data file doesn\'t found (%s)', usageFile);
process.exit(2);
}
usageData = fs.readFileSync(usageFile, 'utf-8');
try {
usageData = JSON.parse(usageData);
} catch (e) {
console.error('Usage data parse error (%s)', usageFile);
process.exit(2);
}
}
readFromStream(inputStream, function(source) {

@@ -228,2 +247,3 @@ var time = process.hrtime();

sourceMap: sourceMap.output,
usage: usageData,
restructure: !structureOptimisationOff,

@@ -230,0 +250,0 @@ debug: debug

@@ -1,2 +0,2 @@

module.exports = function(node, item, list) {
module.exports = function cleanIdentifier(node, item, list) {
// remove useless universal selector

@@ -3,0 +3,0 @@ if (this.selector !== null && node.name === '*') {

@@ -0,1 +1,2 @@

var walk = require('../ast/walk.js').all;
var handlers = {

@@ -9,6 +10,8 @@ Space: require('./Space.js'),

module.exports = function(node, item, list) {
if (handlers.hasOwnProperty(node.type)) {
handlers[node.type].call(this, node, item, list);
}
module.exports = function(ast, usageData) {
walk(ast, function(node, item, list) {
if (handlers.hasOwnProperty(node.type)) {
handlers[node.type].call(this, node, item, list, usageData);
}
});
};

@@ -1,2 +0,35 @@

module.exports = function cleanRuleset(node, item, list) {
var hasOwnProperty = Object.prototype.hasOwnProperty;
function cleanUnused(node, usageData) {
return node.selector.selectors.each(function(selector, item, list) {
var hasUnused = selector.sequence.some(function(node) {
switch (node.type) {
case 'Class':
return usageData.classes && !hasOwnProperty.call(usageData.classes, node.name);
case 'Id':
return usageData.ids && !hasOwnProperty.call(usageData.ids, node.name);
case 'Identifier':
// ignore universal selector
if (node.name !== '*') {
// TODO: remove toLowerCase when type selectors will be normalized
return usageData.tags && !hasOwnProperty.call(usageData.tags, node.name.toLowerCase());
}
break;
}
});
if (hasUnused) {
list.remove(item);
}
});
}
module.exports = function cleanRuleset(node, item, list, usageData) {
if (usageData) {
cleanUnused(node, usageData);
}
if (node.selector.selectors.isEmpty() ||

@@ -3,0 +36,0 @@ node.block.declarations.isEmpty()) {

@@ -0,1 +1,2 @@

var walk = require('../ast/walk.js').all;
var handlers = {

@@ -15,6 +16,8 @@ Atrule: require('./Atrule.js'),

module.exports = function(node, item, list) {
if (handlers.hasOwnProperty(node.type)) {
handlers[node.type].call(this, node, item, list);
}
module.exports = function(ast) {
walk(ast, function(node, item, list) {
if (handlers.hasOwnProperty(node.type)) {
handlers[node.type].call(this, node, item, list);
}
});
};

@@ -1,8 +0,8 @@

var List = require('../utils/list.js');
var convertToInternal = require('./ast/gonzalesToInternal.js');
var convertToGonzales = require('./ast/internalToGonzales.js');
var internalWalkAll = require('./ast/walk.js').all;
var List = require('../utils/list');
var usageUtils = require('./usage');
var convertToInternal = require('./ast/gonzalesToInternal');
var convertToGonzales = require('./ast/internalToGonzales');
var clean = require('./clean');
var compress = require('./compress');
var restructureAst = require('./restructure');
var restructureBlock = require('./restructure');

@@ -52,3 +52,3 @@ function injectInfo(token) {

function compressBlock(ast, restructuring, num, logger) {
function compressBlock(ast, usageData, num, logger) {
logger('Compress block #' + num, null, true);

@@ -61,15 +61,10 @@

// remove useless
internalWalkAll(internalAst, clean);
// remove redundant
clean(internalAst, usageData);
logger('clean', internalAst);
// compress nodes
internalWalkAll(internalAst, compress);
compress(internalAst, usageData);
logger('compress', internalAst);
// structure optimisations
if (restructuring) {
restructureAst(internalAst, logger);
}
return internalAst;

@@ -79,3 +74,2 @@ }

module.exports = function compress(ast, options) {
ast = ast || [{}, 'stylesheet'];
options = options || {};

@@ -93,3 +87,7 @@

var blockRules;
var blockMode = false;
var usageData = false;
ast = ast || [{}, 'stylesheet'];
if (typeof ast[0] === 'string') {

@@ -99,6 +97,27 @@ injectInfo([ast]);

if (ast[1] !== 'stylesheet') {
blockMode = true;
ast = [null, 'stylesheet',
[null, 'ruleset',
[null, 'selector',
[null, 'simpleselector', [null, 'ident', 'x']]],
ast
]
];
}
if (options.usage) {
usageData = usageUtils.buildIndex(options.usage);
}
do {
block = readBlock(ast, block.offset);
block.stylesheet.firstAtrulesAllowed = firstAtrulesAllowed;
block.stylesheet = compressBlock(block.stylesheet, restructuring, blockNum++, logger);
block.stylesheet = compressBlock(block.stylesheet, usageData, blockNum++, logger);
// structure optimisations
if (restructuring) {
restructureBlock(block.stylesheet, usageData, logger);
}
blockRules = block.stylesheet.rules;

@@ -141,13 +160,16 @@

if (!options.outputAst || options.outputAst === 'gonzales') {
return convertToGonzales({
if (blockMode) {
result = result.first().block;
} else {
result = {
type: 'StyleSheet',
rules: result
});
};
}
return {
type: 'StyleSheet',
rules: result
};
if (!options.outputAst || options.outputAst === 'gonzales') {
return convertToGonzales(result);
}
return result;
};
var utils = require('./utils.js');
module.exports = function initialMergeRuleset(node, item, list) {
var selector = node.selector.selectors;
var block = node.block;
var selectors = node.selector.selectors;
var declarations = node.block.declarations;
list.prevUntil(item.prev, function(prev) {
// skip non-ruleset node if safe
if (prev.type !== 'Ruleset') {
return true;
return utils.unsafeToSkipNode.call(selectors, prev);
}
if (node.pseudoSignature !== prev.pseudoSignature) {
return true;
}
var prevSelectors = prev.selector.selectors;
var prevDeclarations = prev.block.declarations;
var prevSelector = prev.selector.selectors;
var prevBlock = prev.block;
// try to join rulesets with equal pseudo signature
if (node.pseudoSignature === prev.pseudoSignature) {
// try to join by selectors
if (utils.isEqualLists(prevSelectors, selectors)) {
prevDeclarations.appendList(declarations);
list.remove(item);
return true;
}
// try to join by selectors
if (utils.isEqualLists(prevSelector, selector)) {
prevBlock.declarations.appendList(block.declarations);
list.remove(item);
return true;
// try to join by declarations
if (utils.isEqualLists(declarations, prevDeclarations)) {
utils.addSelectors(prevSelectors, selectors);
list.remove(item);
return true;
}
}
// try to join by properties
var diff = utils.compareDeclarations(block.declarations, prevBlock.declarations);
if (!diff.ne1.length && !diff.ne2.length) {
utils.addToSelector(prevSelector, selector);
list.remove(item);
return true;
}
// go to next ruleset if simpleselectors has no equal specificity and element selector
return selector.some(function(a) {
return prevSelector.some(function(b) {
return a.compareMarker === b.compareMarker;
});
});
// go to prev ruleset if has no selector similarities
return utils.hasSimilarSelectors(selectors, prevSelectors);
});
};

@@ -383,3 +383,3 @@ var List = require('../../utils/list.js');

module.exports = function restructBlock(ast, declarationMarker) {
module.exports = function restructBlock(ast, indexer) {
var stylesheetMap = {};

@@ -417,3 +417,3 @@ var shortDeclarations = [];

processShorthands(shortDeclarations, declarationMarker);
processShorthands(shortDeclarations, indexer.declaration);
};

@@ -70,4 +70,10 @@ var resolveProperty = require('../ast/names.js').property;

declaration.value.sequence.each(function(node) {
declaration.value.sequence.each(function walk(node) {
switch (node.type) {
case 'Argument':
case 'Value':
case 'Braces':
node.sequence.each(walk);
break;
case 'Identifier':

@@ -109,2 +115,6 @@ var name = node.name;

special[name + '()'] = true;
// check nested tokens too
node.arguments.each(walk);
break;

@@ -111,0 +121,0 @@

var utils = require('./utils.js');
/*
At this step all rules has single simple selector. We try to join by equal
declaration blocks to first rule, e.g.
.a { color: red }
b { ... }
.b { color: red }
->
.a, .b { color: red }
b { ... }
*/
module.exports = function mergeRuleset(node, item, list) {
var selector = node.selector.selectors;
var block = node.block.declarations;
var nodeCompareMarker = selector.first().compareMarker;
var selectors = node.selector.selectors;
var declarations = node.block.declarations;
var nodeCompareMarker = selectors.first().compareMarker;
var skippedCompareMarkers = {};
list.nextUntil(item.next, function(next, nextItem) {
// skip non-ruleset node if safe
if (next.type !== 'Ruleset') {
return true;
return utils.unsafeToSkipNode.call(selectors, next);
}

@@ -19,3 +32,3 @@

var nextFirstSelector = next.selector.selectors.head;
var nextBlock = next.block.declarations;
var nextDeclarations = next.block.declarations;
var nextCompareMarker = nextFirstSelector.data.compareMarker;

@@ -29,5 +42,5 @@

// try to join by selectors
if (selector.head === selector.tail) {
if (selector.first().id === nextFirstSelector.data.id) {
block.appendList(nextBlock);
if (selectors.head === selectors.tail) {
if (selectors.first().id === nextFirstSelector.data.id) {
declarations.appendList(nextDeclarations);
list.remove(nextItem);

@@ -39,14 +52,10 @@ return;

// try to join by properties
if (utils.isEqualDeclarations(block, nextBlock)) {
if (utils.isEqualDeclarations(declarations, nextDeclarations)) {
var nextStr = nextFirstSelector.data.id;
selector.some(function(data, item) {
selectors.some(function(data, item) {
var curStr = data.id;
if (nextStr === curStr) {
return true;
}
if (nextStr < curStr) {
selector.insert(nextFirstSelector, item);
selectors.insert(nextFirstSelector, item);
return true;

@@ -56,3 +65,3 @@ }

if (!item.next) {
selector.insert(nextFirstSelector);
selectors.insert(nextFirstSelector);
return true;

@@ -59,0 +68,0 @@ }

@@ -27,5 +27,9 @@ var List = require('../../utils/list.js');

function inList(selector) {
return selector.compareMarker in this;
}
module.exports = function restructRuleset(node, item, list) {
var avoidRulesMerge = this.stylesheet.avoidRulesMerge;
var selector = node.selector.selectors;
var selectors = node.selector.selectors;
var block = node.block;

@@ -35,7 +39,8 @@ var skippedCompareMarkers = Object.create(null);

list.prevUntil(item.prev, function(prev, prevItem) {
// skip non-ruleset node if safe
if (prev.type !== 'Ruleset') {
return true;
return utils.unsafeToSkipNode.call(selectors, prev);
}
var prevSelector = prev.selector.selectors;
var prevSelectors = prev.selector.selectors;
var prevBlock = prev.block;

@@ -48,13 +53,8 @@

// try prev ruleset if simpleselectors has no equal specifity and element selector
var prevSelectorCursor = prevSelector.head;
while (prevSelectorCursor) {
if (prevSelectorCursor.data.compareMarker in skippedCompareMarkers) {
return true;
}
prevSelectorCursor = prevSelectorCursor.next;
if (prevSelectors.some(inList, skippedCompareMarkers)) {
return true;
}
// try to join by selectors
if (utils.isEqualLists(prevSelector, selector)) {
if (utils.isEqualLists(prevSelectors, selectors)) {
prevBlock.declarations.appendList(block.declarations);

@@ -73,3 +73,3 @@ list.remove(item);

// equal blocks
utils.addToSelector(selector, prevSelector);
utils.addSelectors(selectors, prevSelectors);
list.remove(prevItem);

@@ -82,17 +82,17 @@ return true;

// prevBlock is subset block
var selectorLength = calcSelectorLength(selector);
var selectorLength = calcSelectorLength(selectors);
var blockLength = calcDeclarationsLength(diff.eq); // declarations length
if (selectorLength < blockLength) {
utils.addToSelector(prevSelector, selector);
node.block.declarations = new List(diff.ne1);
utils.addSelectors(prevSelectors, selectors);
block.declarations = new List(diff.ne1);
}
} else if (!diff.ne1.length && diff.ne2.length) {
// node is subset of prevBlock
var selectorLength = calcSelectorLength(prevSelector);
var selectorLength = calcSelectorLength(prevSelectors);
var blockLength = calcDeclarationsLength(diff.eq); // declarations length
if (selectorLength < blockLength) {
utils.addToSelector(selector, prevSelector);
prev.block.declarations = new List(diff.ne2);
utils.addSelectors(selectors, prevSelectors);
prevBlock.declarations = new List(diff.ne2);
}

@@ -105,3 +105,3 @@ } else {

info: {},
selectors: utils.addToSelector(prevSelector.copy(), selector)
selectors: utils.addSelectors(prevSelectors.copy(), selectors)
};

@@ -126,4 +126,4 @@ var newBlockLength = calcSelectorLength(newSelector.selectors) + 2; // selectors length + curly braces length

node.block.declarations = new List(diff.ne1);
prev.block.declarations = new List(diff.ne2.concat(diff.ne2overrided));
block.declarations = new List(diff.ne1);
prevBlock.declarations = new List(diff.ne2.concat(diff.ne2overrided));
list.insert(list.createItem(newRuleset), prevItem);

@@ -136,3 +136,3 @@ return true;

prevSelector.each(function(data) {
prevSelectors.each(function(data) {
skippedCompareMarkers[data.compareMarker] = true;

@@ -139,0 +139,0 @@ });

var internalWalkRules = require('../ast/walk.js').rules;
var internalWalkRulesRight = require('../ast/walk.js').rulesRight;
var translate = require('../ast/translate.js');
var prepare = require('./prepare/index.js');

@@ -13,19 +12,3 @@ var initialMergeRuleset = require('./1-initialMergeRuleset.js');

function Index() {
this.seed = 0;
this.map = Object.create(null);
}
Index.prototype.resolve = function(str) {
var index = this.map[str];
if (!index) {
index = ++this.seed;
this.map[str] = index;
}
return index;
};
module.exports = function(ast, debug) {
module.exports = function(ast, usageData, debug) {
function walkRulesets(name, fn) {

@@ -61,31 +44,15 @@ internalWalkRules(ast, function(node, item, list) {

var declarationMarker = (function() {
var names = new Index();
var values = new Index();
return function markDeclaration(node) {
// node.id = translate(node);
var property = node.property.name;
var value = translate(node.value);
node.id = names.resolve(property) + (values.resolve(value) << 12);
node.length = property.length + 1 + value.length;
return node;
};
})();
// prepare ast for restructing
internalWalkRules(ast, function(node) {
prepare(node, declarationMarker);
});
var indexer = prepare(ast, usageData);
debug('prepare', ast);
// todo: remove initial merge
walkRulesetsRight('initialMergeRuleset', initialMergeRuleset);
// NOTE: direction should be left to right, since rulesets merge to left
// ruleset. When direction right to left unmerged rulesets may prevent lookup
// TODO: remove initial merge
walkRulesets('initialMergeRuleset', initialMergeRuleset);
walkAtrules('mergeAtrule', mergeAtrule);
walkRulesetsRight('disjoinRuleset', disjoinRuleset);
restructShorthand(ast, declarationMarker);
restructShorthand(ast, indexer);
debug('restructShorthand', ast);

@@ -92,0 +59,0 @@

@@ -0,10 +1,12 @@

var internalWalkRules = require('../../ast/walk.js').rules;
var resolveKeyword = require('../../ast/names.js').keyword;
var translate = require('../../ast/translate.js');
var createDeclarationIndexer = require('./createDeclarationIndexer.js');
var processSelector = require('./processSelector.js');
module.exports = function walk(node, markDeclaration) {
function walk(node, markDeclaration, usageData) {
switch (node.type) {
case 'Ruleset':
node.block.declarations.each(markDeclaration);
processSelector(node);
processSelector(node, usageData);
break;

@@ -31,1 +33,13 @@

};
module.exports = function prepare(ast, usageData) {
var markDeclaration = createDeclarationIndexer();
internalWalkRules(ast, function(node) {
walk(node, markDeclaration, usageData);
});
return {
declaration: markDeclaration
};
};

@@ -21,3 +21,3 @@ var translate = require('../../ast/translate.js');

module.exports = function freeze(node) {
module.exports = function freeze(node, usageData) {
var pseudos = Object.create(null);

@@ -27,19 +27,19 @@ var hasPseudo = false;

node.selector.selectors.each(function(simpleSelector) {
var list = simpleSelector.sequence;
var last = list.tail;
var tagName = '*';
var scope = 0;
while (last && last.prev && last.prev.data.type !== 'Combinator') {
last = last.prev;
}
simpleSelector.sequence.some(function(node) {
switch (node.type) {
case 'Class':
if (usageData && usageData.scopes) {
var classScope = usageData.scopes[node.name] || 0;
if (last && last.data.type === 'Identifier') {
tagName = last.data.name;
}
if (scope !== 0 && classScope !== scope) {
throw new Error('Selector can\'t has classes from different scopes: ' + translate(simpleSelector));
}
simpleSelector.compareMarker = specificity(simpleSelector) + ',' + tagName;
simpleSelector.id = translate(simpleSelector);
scope = classScope;
}
break;
simpleSelector.sequence.each(function(node) {
switch (node.type) {
case 'PseudoClass':

@@ -63,4 +63,20 @@ if (!nonFreezePseudoClasses.hasOwnProperty(node.name)) {

break;
case 'Negation':
pseudos.not = true;
hasPseudo = true;
break;
case 'Identifier':
tagName = node.name;
break;
case 'Combinator':
tagName = '*';
break;
}
});
simpleSelector.id = translate(simpleSelector);
simpleSelector.compareMarker = specificity(simpleSelector) + ',' + tagName + (scope ? ',' + scope : '');
});

@@ -67,0 +83,0 @@

@@ -75,3 +75,3 @@ var hasOwnProperty = Object.prototype.hasOwnProperty;

function addToSelector(dest, source) {
function addSelectors(dest, source) {
source.each(function(sourceData) {

@@ -101,2 +101,36 @@ var newStr = sourceData.id;

// check if simpleselectors has no equal specificity and element selector
function hasSimilarSelectors(selectors1, selectors2) {
return selectors1.some(function(a) {
return selectors2.some(function(b) {
return a.compareMarker === b.compareMarker;
});
});
}
// test node can't to be skipped
function unsafeToSkipNode(node) {
switch (node.type) {
case 'Ruleset':
// unsafe skip ruleset with selector similarities
return hasSimilarSelectors(node.selector.selectors, this);
case 'Atrule':
// can skip at-rules with blocks
if (node.block) {
// non-stylesheet blocks are safe to skip since have no selectors
if (node.block.type !== 'StyleSheet') {
return false;
}
// unsafe skip at-rule if block contains something unsafe to skip
return node.block.rules.some(unsafeToSkipNode, this);
}
break;
}
// unsafe by default
return true;
}
module.exports = {

@@ -106,3 +140,5 @@ isEqualLists: isEqualLists,

compareDeclarations: compareDeclarations,
addToSelector: addToSelector
addSelectors: addSelectors,
hasSimilarSelectors: hasSimilarSelectors,
unsafeToSkipNode: unsafeToSkipNode
};

@@ -57,19 +57,24 @@ var parse = require('./parser');

function buildCompressOptions(options) {
function copy(obj) {
var result = {};
for (var key in options) {
result[key] = options[key];
for (var key in obj) {
result[key] = obj[key];
}
result.outputAst = 'internal';
return result;
}
if (typeof result.logger !== 'function' && options.debug) {
result.logger = createDefaultLogger(options.debug);
function buildCompressOptions(options) {
options = copy(options);
options.outputAst = 'internal';
if (typeof options.logger !== 'function' && options.debug) {
options.logger = createDefaultLogger(options.debug);
}
return result;
return options;
}
var minify = function(source, options) {
function minify(context, source, options) {
options = options || {};

@@ -82,3 +87,3 @@

var ast = debugOutput('parsing', options, new Date(),
parse(source, 'stylesheet', {
parse(source, context, {
filename: filename,

@@ -110,4 +115,12 @@ positions: Boolean(options.sourceMap),

return result;
}
function minifyStylesheet(source, options) {
return minify('stylesheet', source, options);
};
function minifyBlock(source, options) {
return minify('declarations', source, options);
}
module.exports = {

@@ -117,3 +130,4 @@ version: require('../package.json').version,

// main method
minify: minify,
minify: minifyStylesheet,
minifyBlock: minifyBlock,

@@ -120,0 +134,0 @@ // utils

@@ -32,17 +32,13 @@ 'use strict';

'atkeyword': getAtkeyword,
'atruleb': getAtrule,
'atruler': getAtrule,
'atrules': getAtrule,
'attrib': getAttrib,
'attrselector': getAttrselector,
'block': getBlock,
'atrule': getAtrule,
'attribute': getAttribute,
'block': getBlockWithBrackets,
'braces': getBraces,
'clazz': getClass,
'class': getClass,
'combinator': getCombinator,
'comment': getComment,
'declaration': getDeclaration,
'declarations': getBlock,
'dimension': getDimension,
'filter': getDeclaration,
'functionExpression': getOldIEExpression,
'funktion': getFunction,
'function': getFunction,
'ident': getIdentifier,

@@ -57,4 +53,4 @@ 'important': getImportant,

'property': getProperty,
'pseudoc': getPseudoc,
'pseudoe': getPseudoe,
'pseudoClass': getPseudoClass,
'pseudoElement': getPseudoElement,
'ruleset': getRuleset,

@@ -70,3 +66,17 @@ 'selector': getSelector,

'value': getValue,
'vhash': getVhash
'vhash': getVhash,
// TODO: remove in 2.0
// for backward capability
'atruleb': getAtrule,
'atruler': getAtrule,
'atrules': getAtrule,
'attrib': getAttribute,
'attrselector': getAttrselector,
'clazz': getClass,
'filter': getDeclaration,
'functionExpression': getOldIEExpression,
'funktion': getFunction,
'pseudoc': getPseudoClass,
'pseudoe': getPseudoElement
};

@@ -235,3 +245,3 @@

node[1] = NodeType.AtrulebType;
node.push(getBlock());
node.push(getBlockWithBrackets());
} else {

@@ -287,3 +297,3 @@ node[1] = NodeType.AtrulerType;

getSelector(),
getBlock()
getBlockWithBrackets()
];

@@ -359,3 +369,3 @@ }

case TokenType.LeftSquareBracket:
node.push(getAttrib());
node.push(getAttribute());
break;

@@ -390,7 +400,16 @@

function getBlock() {
var node = [getInfo(pos), NodeType.BlockType];
function getBlockWithBrackets() {
var info = getInfo(pos);
var node;
eat(TokenType.LeftCurlyBracket);
node = getBlock(info);
eat(TokenType.RightCurlyBracket);
return node;
}
function getBlock(info) {
var node = [info || getInfo(pos), NodeType.BlockType];
scan:

@@ -422,4 +441,2 @@ while (pos < tokens.length) {

eat(TokenType.RightCurlyBracket);
return node;

@@ -560,2 +577,6 @@ }

case TokenType.LowLine:
case TokenType.Identifier:
break;
case TokenType.FullStop:

@@ -591,9 +612,3 @@ case TokenType.DecimalNumber:

parseError('Unexpected input');
break;
case TokenType.HyphenMinus:
case TokenType.LowLine:
case TokenType.Identifier:
break;
default:

@@ -615,3 +630,3 @@ parseError('Unexpected input');

// '[' S* attrib_name attrib_match [ IDENT | STRING ] S* attrib_flags? ']'
function getAttrib() {
function getAttribute() {
var node = [getInfo(pos), NodeType.AttribType];

@@ -1492,3 +1507,3 @@

if (next.type === TokenType.Colon) {
return getPseudoe();
return getPseudoElement();
}

@@ -1501,7 +1516,7 @@

return getPseudoc();
return getPseudoClass();
}
// :: ident
function getPseudoe() {
function getPseudoElement() {
eat(TokenType.Colon);

@@ -1514,3 +1529,3 @@ eat(TokenType.Colon);

// : ( ident | function )
function getPseudoc() {
function getPseudoClass() {
var startPos = pos;

@@ -1599,3 +1614,3 @@ var node = eat(TokenType.Colon) && getIdentifier();

module.exports = function parse(source, rule, options) {
module.exports = function parse(source, context, options) {
var ast;

@@ -1620,9 +1635,9 @@

filename = options.filename || '<unknown>';
rule = rule || 'stylesheet';
context = context || 'stylesheet';
pos = 0;
tokens = tokenize(source, blockMode.hasOwnProperty(rule), options.line, options.column);
tokens = tokenize(source, blockMode.hasOwnProperty(context), options.line, options.column);
if (tokens.length) {
ast = rules[rule]();
ast = rules[context]();
}

@@ -1632,4 +1647,4 @@

if (!ast && rule === 'stylesheet') {
ast = [{}, rule];
if (!ast && context === 'stylesheet') {
ast = [{}, context];
}

@@ -1636,0 +1651,0 @@

{
"name": "csso",
"version": "1.7.1",
"version": "1.8.0",
"description": "CSSO (CSS Optimizer) is a CSS minifier with structural optimisations",

@@ -37,2 +37,3 @@ "keywords": [

"rules": {
"no-duplicate-case": 2,
"no-undef": 2,

@@ -43,7 +44,9 @@ "no-unused-vars": [2, {"vars": "all", "args": "after-used"}]

"scripts": {
"test": "jscs lib && eslint lib test && mocha --reporter dot",
"test": "mocha --reporter dot",
"codestyle": "jscs lib && eslint lib test",
"codestyle-and-test": "npm run codestyle && npm test",
"hydrogen": "node --trace-hydrogen --trace-phase=Z --trace-deopt --code-comments --hydrogen-track-positions --redirect-code-traces --redirect-code-traces-to=code.asm --trace_hydrogen_file=code.cfg --print-opt-code bin/csso --stat -o /dev/null",
"coverage": "istanbul cover _mocha -- -R dot",
"coveralls": "istanbul cover _mocha --report lcovonly -- -R dot && cat ./coverage/lcov.info | coveralls",
"travis": "npm run test && npm run coveralls",
"travis": "npm run codestyle-and-test && npm run coveralls",
"browserify": "browserify --standalone csso lib/index.js | uglifyjs --compress --mangle -o dist/csso-browser.js",

@@ -50,0 +53,0 @@ "gh-pages": "git clone -b gh-pages https://github.com/css/csso.git .gh-pages && npm run browserify && cp dist/csso-browser.js .gh-pages/ && cd .gh-pages && git commit -am \"update\" && git push && cd .. && rm -rf .gh-pages",

@@ -9,4 +9,4 @@ [![NPM version](https://img.shields.io/npm/v/csso.svg)](https://www.npmjs.com/package/csso)

[![Originated by Yandex](https://github.com/css/csso/blob/master/docs/yandex.png)](https://www.yandex.com/)
[![Sponsored by Avito](https://github.com/css/csso/blob/master/docs/avito.png)](https://www.avito.ru/)
[![Originated by Yandex](https://cdn.rawgit.com/css/csso/8d1b89211ac425909f735e7d5df87ee16c2feec6/docs/yandex.svg)](https://www.yandex.com/)
[![Sponsored by Avito](https://cdn.rawgit.com/css/csso/8d1b89211ac425909f735e7d5df87ee16c2feec6/docs/avito.svg)](https://www.avito.ru/)

@@ -38,7 +38,8 @@ ## Usage

-i, --input <filename> Input file
--input-map <source> Input source map. Possible values: none, auto (default) or <filename>
-m, --map <destination> Generate source map. Possible values: none (default), inline, file or <filename>
--input-map <source> Input source map: none, auto (default) or <filename>
-m, --map <destination> Generate source map: none (default), inline, file or <filename>
-o, --output <filename> Output file (result outputs to stdout if not set)
--restructure-off Turns structure minimization off
--stat Output statistics in stderr
-u, --usage <filenane> Usage data file
-v, --version Output version

@@ -74,4 +75,4 @@ ```

- `none` (default) – don't generate source map
- `inline` – generate map add it into result content (via `/*# sourceMappingURL=application/json;base64,...base64 encoded map... */`)
- `file` – generate map and write it into file with same name as output file, but with `.map` extension; in this case `--output` option is required
- `inline` – add source map into result CSS (via `/*# sourceMappingURL=application/json;base64,... */`)
- `file` – write source map into file with same name as output file, but with `.map` extension (in this case `--output` option is required)
- any other values treat as filename for generated source map

@@ -83,17 +84,101 @@

> csso my.css --map inline
> csso my.css --map file --output my.min.css
> csso my.css -o my.min.css -m maps/my.min.map
> csso my.css --output my.min.css --map file
> csso my.css --output my.min.css --map maps/my.min.map
```
Input can has a source map. Use `--input-map` option to specify input source map if needed. Possible values for option:
Use `--input-map` option to specify input source map if needed. Possible values for option:
- `auto` (auto) - attempt to fetch input source map by follow steps:
- try to fetch inline map from source
- try to fetch map filename from source and read its content
- (when `--input` is specified) check for file with same name as input but with `.map` extension exists and read its content
- `auto` (default) - attempt to fetch input source map by follow steps:
- try to fetch inline map from input
- try to fetch source map filename from input and read its content
- (when `--input` is specified) check file with same name as input file but with `.map` extension exists and read its content
- `none` - don't use input source map; actually it's using to disable `auto`-fetching
- any other values treat as filename for input source map
> NOTE: Input source map is using only if source map is generating.
Generally you shouldn't care about input source map since defaults behaviour (`auto`) covers most use cases.
> NOTE: Input source map is using only if output source map is generating.
### Usage data
`CSSO` can use data about how `CSS` is using for better compression. File with this data (`JSON` format) can be set using `--usage` option. Usage data may contain follow sections:
- `tags` – white list of tags
- `ids` – white list of ids
- `classes` – white list of classes
- `scopes` – groups of classes which never used with classes from other groups on single element
All sections are optional. Value of `tags`, `ids` and `classes` should be array of strings, value of `scopes` should be an array of arrays of strings. Other values are ignoring.
#### Selector filtering
`tags`, `ids` and `classes` are using on clean stage to filter selectors that contains something that not in list. Selectors are filtering only by those kind of simple selector which white list is specified. For example, if only `tags` list is specified then type selectors are checking, and if selector hasn't any type selector (or even any type selector) it isn't filter.
> `ids` and `classes` comparison is case sensetive, `tags` – is not.
Input CSS:
```css
* { color: green; }
ul, ol, li { color: blue; }
UL.foo, span.bar { color: red; }
```
Usage data:
```json
{
"tags": ["ul", "LI"]
}
```
Result CSS:
```css
*{color:green}ul,li{color:blue}ul.foo{color:red}
```
#### Scopes
Scopes is designed for CSS scope isolation solutions such as [css-modules](https://github.com/css-modules/css-modules). Scopes are similar to namespaces and defines lists of class names that exclusively used on some markup. This information allows the optimizer to move rulesets more agressive. Since it assumes selectors from different scopes can't to be matched on the same element. That leads to better ruleset merging.
Suppose we have a file:
```css
.module1-foo { color: red; }
.module1-bar { font-size: 1.5em; background: yellow; }
.module2-baz { color: red; }
.module2-qux { font-size: 1.5em; background: yellow; width: 50px; }
```
It can be assumed that first two rules never used with second two on the same markup. But trully speaking we cann't know that for sure without markup. The optimizer doesn't know it eather and will perform safe transformations only. The result will be the same as input but with no spaces and some semicolons:
```css
.module1-foo{color:red}.module1-bar{font-size:1.5em;background:#ff0}.module2-baz{color:red}.module2-qux{font-size:1.5em;background:#ff0;width:50px}
```
But with usage data `CSSO` can get better output. If follow usage data is provided:
```json
{
"scopes": [
["module1-foo", "module1-bar"],
["module2-bar", "module2-baz"]
]
}
```
New result (29 bytes extra saving):
```css
.module1-foo,.module2-baz{color:red}.module1-bar,.module2-qux{font-size:1.5em;background:#ff0}.module2-qux{width:50px}
```
If class name doesn't specified in `scopes` it's considered that it belongs to default "scope". `scopes` doesn't affect `classes`. If class name present in `scopes` but missed in `classes` (both sections specified) it will be filtered.
Note that class name can't be specified in several scopes. Also selector can't has classes from different scopes. In both cases an exception throws.
Currently the optimizer doesn't care about out-of-bounds selectors order changing safety (i.e. selectors that may be matched to elements with no class name of scope, e.g. `.scope div` or `.scope ~ :last-child`) since assumes scoped CSS modules doesn't relay on it's order. It may be fix in future if to be an issue.
### API

@@ -100,0 +185,0 @@

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc